bbs

package module
v0.0.0-...-9dfbb06 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 2, 2014 License: BSD-2-Clause Imports: 9 Imported by: 3

README

BBS Protocol

(first draft)

The BBS protocol is a generic JSON protocol designed to fit most needs of various kinds of message boards (internet forums, bulletin boards, etc.) It is designed to be lightweight, and should be as painless as possible to implement. It could be used for a native forums browsing app.

This project

This project is a simple server that speaks BBS. It also contains a tiny command line client for testing purposes. Some nice documentation is under construction as well. BSD licensed.

Software supporting BBS
Is the protocol stable?

No, but it's getting there. See: https://github.com/guregu/bbs/issues/1

Protocol Reference

A message board (or "BBS") has a single endpoint, which we shall refer to as the BBS endpoint URL. Example:

http://bbs.tiko.jp/bbs
http://somesite.com/boards/bbs-gateway.php

Communication between the client and server currently consists of HTTP POSTs. The whole body of the POST should be a JSON object. The entire reply should also be a JSON object. You can put whatever you'd like there for GET requests (for example, a web client).

Ideally I would rather have a nice RESTful interface, but this allows for:

  • No need to impose our URL structure on other sites (just plop down bbs-gateway.php, or whatever)
  • We can have the same wire protocol for websockets, which will be included in the next version of the protocol.

JSON Structure

Every request and response should take the form of a JSON object like:

{
	"cmd": "[command name]"
    ...
}

Where [command name] is one of the following commands:

Client commands Server commands
hello hello
login welcome
logout msg
get list
list ok
post error
reply

The rest of the object's contents depend on what kind of command it is. Since this is an extensible protocol, clients should silently ignore fields they don't understand.

Request Flow

Client request command Possible server responses
hello hello
login welcome, error
logout ok
get msg, error
list list, error
post ok, error
reply ok, error

"hello" command (client → server)

Politely greets the server, requesting some general information. The reply should be a "hello" command.

Fields

None.

Example
{
	"cmd": "hello"
}
Notes

Your client will (probably) have a list of BBSs the user has added. You can 'hello' each one to figure out the server name, options, etc., also check if a server is up.

"hello" command (server → client)

Responds with information about the BBS.

Fields
Field name Type Required? Option Description
name string required Server name.
version int required Highest supported BBS protocol version (right now: 0)
desc string required Server description.
secure string optional HTTPS URL to a secure connection for this BBS
options string array optional The options this server supports. (See Options section)
access object required Describes which commands require login (see below)
format string array required Formats this server understands, with the preferred format first. (See Formats section)
lists string array required Describes the lists available (see "list" command)
server string required Server version string, can be anything.
access object
Field name Type Required? Option Description
guest string array required Commands that don't require logging in.
user string array required Commands that require logging in.
Example
{
    "cmd": "hello",
    "name": "ETI Gateway",
    "version": 0,
    "desc": "ETI -> BBS Gateway",
    "options": [
        "tags",
        "avatars",
        "usertitles",
        "filter"
    ],
    "access": {
        "guest": [
            "hello",
            "login",
            "logout"
        ],
        "user": [
            "get",
            "list",
            "post",
            "reply"
        ]
    },
    "format": [
        "html",
        "text"
    ],
    "lists": [
        "thread",
        "tag"
    ],
    "server": "eti-relay 0.1"
}
Notes

Every server is required to respond to this command, it help clients set up their layout and know what your server does.

"login" command (client → server)

Used to log in. The reply will be a "welcome" command on success or an "error" command on error. The "welcome" reply should contain a "session" string, which the client will attach to commands from then on out. There is currently no way for servers to specify whether they need a username and password, just one, or none. Expect this command to expand soon. For now, usernames and passwords are required.

Fields
Field name Type Required? Option Description
username string required User name.
password string required Password.
version int required Client's requested protocol version, ideally the server should honor this.
Example
{
    "cmd": "login",
    "username": "Llamaguy",
    "password": "hunter2",
    "version": 0
}
Notes

For servers that require you to log in to post or even read messages, this is a useful command. For read only public servers or anonymous boards, you won't need this. Clients should use a secure (HTTPS) connection to log in if the server has them. Servers specify their secure URL in the "hello" command.

"welcome" command (server → client)

Sent to clients as a response to the "login" command when their log in is successful. It contains a session token that the client should include in requests from now on.

Fields
Field name Type Required? Option Description
session string required Session token.
username string optional The user's properly capitalized username. Optional for anonymous boards.
Example
{
	"cmd": "welcome",
	"session": "3a53192bcdca028d285692a731b041e1",
	"username": "LlamaGuy"
}
Notes

You must include this session token in further requests to preform them as a logged in user. The username field is useful to get a properly capitalized username, or to inform people of their username for websites that use e-mail for log in, etc.

"error" command (server → client)

Sent as a response to client commands that did not successfuly do what they were supposed to. In this case, the wrt field should be the client command that failed. There is a special case that if the client sends an invalid (expired, etc.) session token, that wrt should be "session" instead.

Fields
Field name Type Required? Option Description
wrt string required The name of the command that failed, or "session" for session token issues.
error string optional A description of the error.
Notes

The "error" command is often sent when a client requests to do something that the server doesn't allow for. In this case, the client should generally display the error message. If wrt is "session", that means the client sent a bad session token, and should log in again.

"logout" command (client → server)

Requests a log out. Currently, there is no confirmation of logging out actually happening. So, if someone not logged in logs out, the server is totally OK with that.

Fields
Field name Type Required? Option Description
session string required Session token.
Example
{
	"cmd": "logout",
	"session": "3a53192bcdca028d285692a731b041e1"
}
Notes

Servers should expire people's sessions after inactivity regardless.

"ok" command (server → client)

This command is sent to clients as a response to a successful command. Many commands, such as "get" have a specific command the server sends as a response (in this example: "msg") and this command is not for them. The "ok" command is sent as a response to commands that do not need more than a simple string in return.

Fields
Field name Type Required? Option Description
wrt string required The name of the command that succeeded.
result string optional Any additional information, can be ignored or omitted.
Example
{
	"cmd": "ok",
	"wrt": "logout"
}
Notes

This command is sent as a response to many different client commands to indicate success. For "post" and "reply", result should be the new thread ID or post ID, but it is allowed to be missing.

"get" command (client → server)

This command is used to request messages from the server. It is used for viewing threads. Servers respond with a "msg" command or an "error" command.

Fields
Field name Type Required? Option Description
id string required Desired thread's ID.
session string optional Session token, if logged in.
range object optional range Desired message range, see below.
filter string optional filter The user ID whose messages you want.
format string optional The desired format. Omit for server default.
range object
Field name Type Required? Option Description
start int required Post number to start from. 1-indexed (1 is the OP)
end int required Post number to end at, inclusive.
Example
{
    "cmd": "get",
    "id": "8382679",
    "session": "3a53192bcdca028d285692a731b041e1",
    "range": {
        "start": 1,
        "end": 50
    },
    "format": "text"
}
Notes

For servers that don't support the "range" option, this command will get every post. For servers that do, it will get the default range if range is omitted.

"msg" command (server → client)

This command is used to send messages (posts) to the client. It is used to display a thread. It is the response to a "get" command. In the future, it will be expanded with real-time features.

Fields
Field name Type Required? Option Description
id string required Thread ID
title string optional Thread title, if any.
range object optional range Range describing the enclosed messages, if any.
closed boolean optional If true, it means the topic can't be posted in.
filter string optional filter The user ID filtered by, if any.
board string required* boards The board this topic belongs to.
tags string array optional tags The tags this thread is associated with, if any.
format string optional The format the following posts are in. Default format if omitted.
messages object array required Posts. See below.
more boolean optional Are there more posts available?
messages objects
Field name Type Required? Option Description
id string required Message ID
user string required? Username
user_id string optional User ID used for filtering threads, getting profiles, etc.
date string optional The date, in no particular format.
body string required The post body. Message text. The content
sig string optional Post signature. Think USENET signatures, not file signatures.
user_title string optional usertitles A little blurb of text. "Custom Title" on SA.
avatar string optional avatars URL to user's full sized avatar.
avatar_thumb string optional avatars URL to user's thumbnail avatar.
img string optional imageboard This post's attached image.
thumb string optional imageboard This post's attached image's thumbnail.
Example
{
    "cmd": "msg",
    "id": "8382679",
    "title": "Who's excited for pizza tomorrow?",
    "range": {
        "start": 1,
        "end": 4
    },
    "tags": [
        "Pizza"
    ],
    "format": "text",
    "messages": [
        {
            "id": "m122677227",
            "user": "scofflaw",
            "user_id": "22490",
            "date": "3/22/2013 09:12:32 AM",
            "body": "i wonder who's gonna win the weekly pizza lottery",
            "sig": "- Scofflaw"
        },
        {
            "id": "m122678244",
            "user": "zekachu",
            "user_id": "21735",
            "date": "3/22/2013 09:33:10 AM",
            "body": ":o i cant wait",
            "sig": "http://zeke.fm\nbirds flying high you know how i feel"
        },
        {
            "id": "m122678304",
            "user": "Johnnybigone",
            "user_id": "9063",
            "date": "3/22/2013 09:34:11 AM",
            "body": "oh man oh man oh man\n"
        },
        {
            "id": "m122678349",
            "user": "Big the Cat500",
            "user_id": "6315",
            "date": "3/22/2013 09:34:53 AM",
            "body": "spoilers zekachu is gonna win it",
            "sig": "Eternal Pirate Gai\nSkies of Arcadia >>>>>> Final Fantasy. Try playing it, sometime.",
            "user_title": "Eternal Pirate"
        }
    ]
}
Notes

This is the meat of your BBS. You can omit nearly everything. I'm leaning towards names being required. Even on anonymous boards, you can set the name as "Anonymous" for everyone. There is no way to request single posts right now, without using range.

"list" command (client → server)

Asks for lists. Like a thread list or a board list. The lists a server supports are given in the "hello" command (lists).

Fields
Field name Type Required? Option Description
type string required The kind of list requested. ("thread", "board", "tag"...)
query string optional For thread lists: the board ID/tag expression. Or blank/missing.
session string optional Session token.
Example
{
	"cmd": "list",
	"type":"thread",
	"query":"Pizza&Crime",
	"session":"3a53192bcdca028d285692a731b041e1"
}

"list" command (server → client)

The response to a client's "list" command.

Fields
Field name Type Required? Option Description
type string required List type (see client "list" command)
query string optional The client's query, if any.
threads object array required* The thread list. Required when type is "thread". See below.
boards object array required* boards Board list. Required when type is "board". See below.
threads object (thread listing)
Field name Type Required? Option Description
id string required Thread ID
title string required Thread title. Can be blank.
user string optional Username of the thread author.
user_id string optional User ID of the thread author.
date string optional Some kind of date. Could be the last post date. Could be the thread creation date, whichever works for you.
posts int optional Post count.
unread_posts int optional Unread post count.
sticky bool optional True if this thread is stickied (pinned), false or omitted otherwise.
closed bool optional True if this thread is closed for posting, false or omitted otherwise.
tags string array optional tags Tags associated with this topic, if any.
img string optional imageboard URL for the image attached to the thread, if any.
thumb string optional imageboard Thumbnail URL for the image attached to the thread.
boards object (board listing)
Field name Type Required? Option Description
id string required boards Board ID
name string optional boards Board name
desc string optional boards Board description
threads int optional boards Thread count
date string optional boards Some kind of date (last post, usually).
Examples

Thread list:

{
    "cmd": "list",
    "type": "thread",
    "query": "Pizza&Crime",
    "threads": [
        {
            "id": "8331156",
            "title": "ITT Wolfpac comments on the Christopher Dorner manifesto while eating pizza",
            "user": "Wolfpac",
            "user_id": "282",
            "date": "2/12/2013 16:20",
            "posts": 9,
            "tags": [
                "LUE",
                "Anarchism"
            ]
        }
    ]
}

Board list:

{
    "cmd": "list",
    "type": "board",
    "boards": [
        {
            "id": "3",
            "name": "/3/ - 3DCG"
        },
        {
            "id": "a",
            "name": "/a/ - Anime & Manga"
        },
        {
            "id": "adv",
            "name": "/adv/ - Advice"
        },
        {
            "id": "an",
            "name": "/an/ - Animals & Nature"
        },
        {
            "id": "asp",
            "name": "/asp/ - Alternative Sports"
        },
        {
            "id": "b",
            "name": "/b/ - Random (NWS)"
        }
    ]
}

"post" command (client → server)

Used for posting new threads.

Fields
Field name Type Required? Option Description
title string required New thread title.
body string required New thread body (post content)
format string optional Format this is in, or default format is omitted.
board string required* boards The board to post to. Required for "boards" option servers.
tags string array optional tags The tags to associate with the new thread.
session string optional Session token.
Example
{
	"cmd": "post",
	"title": "Hello everyone.",
	"body": "Hey guys I'm new here XD",
	"format": "text",
	"session": "3a53192bcdca028d285692a731b041e1"
}
Notes

None.

"reply" command (client → server)

Used for replying to existing threads.

Fields
Field name Type Required? Option Description
to string required Thread ID to reply to
body string required New thread body (post content)
format string optional Format this is in, or default format is omitted.
session string optional Session token.
Example
{
	"cmd": "reply",
	"to": "q/2312321",
	"body": "Why u guys delete my post?????",
	"format": "text",
	"session": "3a53192bcdca028d285692a731b041e1"
}
Notes

None.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Serve

func Serve(address string, path string, fact func() BBS)

Types

type AccessInfo

type AccessInfo struct {
	GuestCommands []string `json:"guest,omitempty"`
	UserCommands  []string `json:"user,omitempty"`
}

guest commands are commands you can use without logging on (e.g. "list", "get") user commands require being logged in first (usually "post" and "reply")

type BBS

type BBS interface {
	Hello() HelloMessage
	Register(m RegisterCommand) (OKMessage, error)
	LogIn(m LoginCommand) bool
	LogOut(m LogoutCommand) OKMessage
	IsLoggedIn() bool
	Get(m GetCommand) (ThreadMessage, error)
	List(m ListCommand) (ListMessage, error)
	Reply(m ReplyCommand) (OKMessage, error)
	Post(m PostCommand) (OKMessage, error)
}

type BBSCommand

type BBSCommand struct {
	Command string `json:"cmd"`
}

type BoardListMessage

type BoardListMessage struct {
	Command string         `json:"cmd"`
	Type    string         `json:"type"`
	Query   string         `json:"query,omitempty"`
	Boards  []BoardListing `json:"boards"`
}

"list" message where type = "board" (server -> client)

type BoardListing

type BoardListing struct {
	ID          string `json:"id"`
	Name        string `json:"name,omitempty"`
	Description string `json:"desc,omitempty"`
	ThreadCount int    `json:"threads,omitempty"`
	PostCount   int    `json:"posts,omitempty"`
	Date        string `json:"date,omitempty"`
}

format for boards in "list"

type Boards

type Boards interface {
	BoardList(m ListCommand) (BoardListMessage, error)
}

type Bookmark

type Bookmark struct {
	ID    string `json:"id,omitempty"`
	Name  string `json:"name"`
	Query string `json:"query"`
}

type BookmarkListMessage

type BookmarkListMessage struct {
	Command   string     `json:"cmd"`
	Type      string     `json:"type"`
	Bookmarks []Bookmark `json:"bookmarks"`
}

type Bookmarks

type Bookmarks interface {
	BookmarkList(m ListCommand) (BookmarkListMessage, error)
}

type ErrorMessage

type ErrorMessage struct {
	Command string `json:"cmd"`
	ReplyTo string `json:"wrt"`
	Error   string `json:"error"`
}

"error" message (server -> client)

var SessionErrorMessage ErrorMessage = Error("session", "bad session")

session expired or invalid? use this

func Error

func Error(wrt, msg string) ErrorMessage

type GetCommand

type GetCommand struct {
	Command  string `json:"cmd"`
	Session  string `json:"session,omitempty"`
	ThreadID string `json:"id"`
	Range    Range  `json:"range"`            //option: "range"
	Filter   string `json:"filter,omitempty"` //option: "filter"
	Format   string `json:"format,omitempty"`
	Token    string `json:"token,omitempty"`
}

"get" command (client -> server)

type HelloMessage

type HelloMessage struct {
	Command         string     `json:"cmd"`
	Name            string     `json:"name"`
	ProtocolVersion int        `json:"version"`
	Description     string     `json:"desc"`
	SecureURL       string     `json:"secure,omitempty"` //https URL, if any
	Options         []string   `json:"options,omitempty"`
	Access          AccessInfo `json:"access"`
	Formats         []string   `json:"format"` //formats the server accepts, the first one should be the primary one
	Lists           []string   `json:"lists"`
	ServerVersion   string     `json:"server"`
	IconURL         string     `json:"icon"`
	// for option "range"
	DefaultRange Range `json:"default_range,omitempty"`
	// for option "realtime"
	RealtimeURL string `json:"realtime"`
}

"hello" message (server -> client)

type ListCommand

type ListCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session,omitempty"`
	Type    string `json:"type"`
	Query   string `json:"query"` //board for "boards", tag expression for "tags" (like "Dogs+Pizza-Anime")
	Token   string `json:"token,omitempty"`
}

"list" command (client -> server)

type ListMessage

type ListMessage struct {
	Command   string          `json:"cmd"`
	Type      string          `json:"type"`
	Query     string          `json:"query,omitempty"`
	Threads   []ThreadListing `json:"threads"`
	NextToken string          `json:"next,omitempty"`
}

"list" message where type = "thread" (server -> client)

type ListenCommand

type ListenCommand struct {
	Command string `json:"cmd"`
	Type    string `json:"type"`
	ID      string `json:"id"`
}

type Listener

type Listener interface {
	Send(interface{})
}

for realtime stuff

type LoginCommand

type LoginCommand struct {
	Command         string `json:"cmd"`
	Username        string `json:"username"`
	Password        string `json:"password"`
	ProtocolVersion int    `json:"version"`

	// for "re-logins" only
	Session string `json:"session,omitempty"`
}

"login" command (client -> server)

type LogoutCommand

type LogoutCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session"`
}

"logout" command (client -> server)

type Message

type Message struct {
	ID                 string `json:"id"`
	Author             string `json:"user"`
	AuthorID           string `json:"user_id,omitempty"`
	Date               string `json:"date,omitempty"`
	Text               string `json:"body"`
	Signature          string `json:"sig,omitempty"`
	AuthorTitle        string `json:"user_title,omitempty"`   //option: "usertitles"
	AvatarURL          string `json:"avatar,omitempty"`       //option: "avatars"
	AvatarThumbnailURL string `json:"avatar_thumb,omitempty"` //option: "avatars"
	PictureURL         string `json:"img,omitempty"`          //option: "imageboard"
	ThumbnailURL       string `json:"thumb,omitempty"`        //option: "imageboard"
}

format for posts used in "msg"

type OKMessage

type OKMessage struct {
	Command string `json:"cmd"`
	ReplyTo string `json:"wrt"`
	Result  string `json:"result,omitempty"`
}

"ok" message (server -> client)

func OK

func OK(wrt string) OKMessage

type PostCommand

type PostCommand struct {
	Command string   `json:"cmd"`
	Session string   `json:"session,omitempty"`
	Title   string   `json:"title"`
	Text    string   `json:"body"`
	Format  string   `json:"format,omitempty"`
	Board   string   `json:"board,omitempty"` //option: "boards"
	Tags    []string `json:"tags,omitempty"`  //option: "tags"
}

"post" command (client -> server)

type Range

type Range struct {
	Start int `json:"start"`
	End   int `json:"end"`
}

From start to end inclusive, starting from 1.

func (Range) Empty

func (r Range) Empty() bool

func (Range) String

func (r Range) String() string

func (Range) Validate

func (r Range) Validate() bool

type Realtime

type Realtime interface {
	Listen(m ListenCommand) (OKMessage, error)
	Part(m ListenCommand) (OKMessage, error)

	// server stuff
	Connect(Listener)
	Bye()
}

type RegisterCommand

type RegisterCommand struct {
	Command  string `json:"cmd"`
	Username string `json:"username"`
	Password string `json:"password"`
	Email    string `json:"email,omitempty"`
}

"register" command (client -> server)

type ReplyCommand

type ReplyCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session,omitempty"`
	To      string `json:"to"`
	Text    string `json:"body"`
	Format  string `json:"format,omitempty"`
}

"reply" command (client -> server)

type Server

type Server struct {
	Sessions *SessionHandler
	Name     string
	WS       http.Handler
	// contains filtered or unexported fields
}

func NewServer

func NewServer(factory func() BBS) *Server

func (*Server) DefaultBBS

func (srv *Server) DefaultBBS() BBS

func (*Server) NewBBS

func (srv *Server) NewBBS() BBS

func (*Server) ServeHTTP

func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*Server) ServeWebsocket

func (srv *Server) ServeWebsocket(socket *websocket.Conn)

type Session

type Session struct {
	SessionID  string
	UserID     string
	BBS        BBS
	LastAction time.Time
}

type SessionHandler

type SessionHandler struct {
	Server *Server
	// contains filtered or unexported fields
}

func NewSessionHandler

func NewSessionHandler(srv *Server) *SessionHandler

func (*SessionHandler) Add

func (sh *SessionHandler) Add(sesh *Session)

func (*SessionHandler) Copy

func (sh *SessionHandler) Copy(from *Session, to *Session)

func (*SessionHandler) Get

func (sh *SessionHandler) Get(sesh string) *Session

func (*SessionHandler) Logout

func (sh *SessionHandler) Logout(sesh string)

func (*SessionHandler) Touch

func (sh *SessionHandler) Touch(sesh string)

func (*SessionHandler) TryLogin

func (sh *SessionHandler) TryLogin(m LoginCommand) *Session

func (*SessionHandler) Upgrade

func (sh *SessionHandler) Upgrade(sesh *Session, m LoginCommand) bool

type ThreadListing

type ThreadListing struct {
	ID           string   `json:"id"`
	Title        string   `json:"title"`
	Author       string   `json:"user,omitempty"`
	AuthorID     string   `json:"user_id,omitempty"`
	Date         string   `json:"date,omitempty"`
	PostCount    int      `json:"posts,omitempty"`
	UnreadPosts  int      `json:"unread_posts,omitempty"`
	Sticky       bool     `json:"sticky,omitempty"` //a sticky (aka pinned) topic
	Closed       bool     `json:"closed,omitempty"` //a closed (aka locked) topic
	Tags         []string `json:"tags,omitempty"`   //option: "tags"
	PictureURL   string   `json:"img,omitempty"`    //option: "imageboard"
	ThumbnailURL string   `json:"thumb,omitempty"`  //option: "imageboard"
}

format for threads in "list"

type ThreadMessage

type ThreadMessage struct {
	Command   string    `json:"cmd"`
	ID        string    `json:"id" bson:"_id"`
	Title     string    `json:"title,omitempty"`
	Range     Range     `json:"range,omitempty"`
	Closed    bool      `json:"closed,omitempty"`
	Filter    string    `json:"filter,omitempty"` //option: "filter"
	Board     string    `json:"board,omitempty"`  //option: "boards"
	Tags      []string  `json:"tags,omitempty"`   //option: "tags"
	Format    string    `json:"format,omitempty"`
	Messages  []Message `json:"messages"`
	Total     int       `json:"total,omitempty"`
	More      bool      `json:"more,omitempty"`
	NextToken string    `json:"next,omitempty"`
}

"msg" message (server -> client) [response to "get"]

func (ThreadMessage) Size

func (t ThreadMessage) Size() int

type TypedMessage

type TypedMessage struct {
	Command string `json:"cmd"`
	Type    string `json:"type"`
}

type UnknownHandler

type UnknownHandler interface {
	Unknown(string, []byte) interface{}
}

type UserCommand

type UserCommand struct {
	Command string `json:"cmd"`
	Session string `json:"session"`
}

type WelcomeMessage

type WelcomeMessage struct {
	Command  string `json:"cmd"`
	Username string `json:"username,omitempty"` //omit for option 'anon'
	Session  string `json:"session"`
}

"welcome" message (server -> client)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL