* ChatDraw ChatDraw lets you write your own tools now. Here's a quick guide on how to get started: ** Getting Started The ~chatDraw~ is an object that lets you add tools. A tool is an object with a ~tool~ function that tells the drawer how to draw your tool thing. Here's an example of how you would add the "freehand" tool: #+BEGIN_SRC javascript function freehandFunction(data, context) { return CanvasUtilities.DrawSolidLine(context, data.oldX, data.oldY, data.x, data.y, data.lineWidth); } var freehandTool = new CanvasDrawerTool(freehandFunction); LocalChatDraw.getDrawer().tools["freehand"] = freehandTool; #+END_SRC First I define the function that tells us how to draw. The ~data~ describes the cursor action and the ~context~ is what to draw on (a javascript canvas 2D context object). I use one of my library functions called ~DrawSolidLine~ to draw a line without anti-aliasing (which you MUST do for chatdraw). That function produces the bounding box of the drawn line; we return this so the ~chatDrawer~ knows what area to redraw. This is an optimization: you do NOT need to return anything from the function. If you do not, each action will simply redraw the whole canvas. ~LocalChatDraw~ is this stupid object that I might get rid of later; ~getDrawer()~ returns the drawing object that describes the chatDraw, and ~tools~ is an associative array of ~CanvasDrawerTools~. The string "freehand" is just the name you want to give your tool; make sure it doesn't collide with other tools. The ~data~ parameter is a ~CursorActionData~ object with the following fields: ~action, x, y, oldX, oldY, startX, startY, onTarget, lineWidth, color~ ~action~ is a bitfield describing which actions are taking place. These are ~CursorAction.Start~, ~CursorAction.End~, ~CursorAction.Drag~, ~CursorAction.Pan~, ~CursorAction.Zoom~, etc. You only need to worry about Start and End, as the chatDraw only sends you Drag events anyway. *** Using the Overlay Some tools require a preview before finally being placed on the canvas. For instance, the line tool should only PLACE the line when it's finished; while you're holding it, it's drawing an overlay. The line tool would look like this: #+BEGIN_SRC javascript function lineFunction(data, context) { if(data.action & CursorActions.End) { return CanvasUtilities.DrawSolidLine(context, data.startX, data.startY, data.x, data.y, data.lineWidth); } } function lineOverlay(data, context) { return CanvasUtilities.DrawSolidLine(context, data.startX, data.startY, data.x, data.y, data.lineWidth); } var lineTool = new CanvasDrawerTool(lineTool, lineOverlay); LocalChatDraw.getDrawer().tools["line"] = lineTool; #+END_SRC Notice that we check to see if the action is "End" before placing the line. The overlay however just always draws the line. I provide the appropriate overlay context for the ~overlay~ function, so it doesn't draw it directly onto the canvas. So, the "tool" functions describe how to actually draw the thing, and the overlay describes what to draw temporarily (which not all tools require: see freehand). *** Adding Tools to the ChatDraw Interface Finally, you probably want to be able to use this tool. For now, just create a new button for the tool: #+BEGIN_SRC javascript var button = LocalChatDraw.createToolButton("🖰", "line"); var buttonArea = document.querySelectorAll("#chatdraw button-area")[1]; buttonArea.insertBefore(button, buttonArea.firstChild); #+END_SRC The first parameter to ~createToolButton~ is the display character, the second is the tool name to select when the button is clicked. If you want the button to be able to cycle through tools like the other buttons in ~chatDraw~, just pass arrays instead of strings like so: #+BEGIN_SRC javascript LocalChatDraw.createToolButton(["char1","char2"], ["tool1","tool2"]); #+END_SRC
#+TITLE: Documentation for CanvasPerformer #+AUTHOR: Randomouscrap * CanvasPerformer ** About ~CanvasPerformer~ is a javascript object for unifying mouse and touch events into simple drag, pan, and zoom events. These events make creating things like image viewers and drawing applications much easier. ~CanvasPerformer~ converts the complicated mess of mouse and touch events into ~CursorActionData~ objects, which describe the location, zoom difference, and action that has been performed. This ~CursorActionData~ is then given to your ~OnAction~ function so you can do what you like with it. ** Example To use ~CanvasPerformer~, you create the object, then "Attach" it to a canvas. You can then detach it later if you are done (it's not necessary though). #+BEGIN_SRC javascript var performer = new CanvasPerformer(); performer.Attach(myCanvas); performer.OnAction = function(data, context) { console.log("Got action!"); }; #+END_SRC Note here that ~data~ is the ~CursorActionData~ for the cursor event, and ~context~ is the 2d context of the canvas you're working with. You perform drawing commands like ~fillRect~ with the context. ** CursorActionData ~CursorActionData~ objects have a few fields: | ~x~ | The X position where the current action took place | | ~y~ | The Y position where the current action took place | | ~action~ | A bitfield describing which actions just happened | | ~zoomDelta~ | How much the zoom changed in this action | ~onTarget~ | Whether or not this event is on the target element | | ~targetElement~ | The target element on which the actions are being performed (but not necessarily ON this target; see onTarget) | The action field is a bitfield made up of a combination of ~CursorAction~. For instance, when the user starts a drag, the action will be ~(CursorAction.Start | CursorAction.Drag)~. Subsequent actions will be ~CursorAction.Drag~, and when the user stops the drag it will be ~(CursorAction.End | CursorAction.Drag)~. The possible actions are: | CursorAction.Start | CursorAction.End | CursorAction.Drag | CursorAction.Pan | CursorAction.Zoom ** Configuring CanvasPerformer The ~CanvasPerformer~ can be configured so that it produces events the way you want it to. For instance, Drag is set to 1 touch or Left Click on the mouse. You can configure these using the following ~CanvasPerformer~ properties (just set them in your object): | DragButton | The mouse button used to make drag events. All mouse button values should be the standard values found in the "buttons" field of mouse events (NOT "button") | PanButton | Button for pan events. Set to left mouse click | WheelZoom | How much to alter zoom per wheel click. The zoom value is arbitrary and not kept track of in CanvasPerformer; it's up to your implementation to determine what a changing zoom means. | DragTouches | The amount of touches required to make a drag event. | ZoomTouches | The amount of touches required to make a zoom event. Note: the current implementation does NOT allow you to set this to anything other than 2 because of the calculations required. | PanTouches | The amount of touches required to make a pan event. Note: the default is 2, so using 2 touches creates both Zoom and Pan ** CanvasPerformer Custom Implementation Example Let's make a super simple Drawing application. We'll start by making our ~OnAction~ function, which will draw a simple line for ~Drag~ events. It might look something like this (remember, ~context~ is the 2D context for a canvas, so you can just draw directly onto context): #+BEGIN_SRC javascript function EasyDraw(data, context) { //Only draw if we're dragging if(data.action & CursorActions.Drag) { //When you start a stroke, it should at least put a point down. These use //CanvasUtilities functions, which are included in the same file as //CanvasPerformer, so you can use these freely. 2 here is the line width if(data.action & CursorActions.Start) CanvasUtilities.DrawCenteredRectangle(context, data.x, data.y, 2, 2); else if(!(data.action & CursorActions.End) && (context.oldX >= 0 || context.oldY >= 0)) CanvasUtilities.DrawSolidLine(context, context.oldX, context.oldY, data.x, data.y, 2); //The "context" will never change, so we can use it to store state //between events. We NEED to know the oldX and oldY positions in order to //draw a line between them. if(data.action & CursorActions.End) { context.oldX = -1; context.oldY = -1; } else { context.oldX = data.x; context.oldY = data.y; } } } #+END_SRC Then we just set it up as usual: #+BEGIN_SRC javascript var performer = new CanvasPerformer(); performer.Attach(myCanvas); performer.OnAction = EasyDraw; #+END_SRC And that's all there is to making a simple drawing application with ~CanvasPerformer~!
#+TITLE: Documentation for Chat WebSocket Interface * About The SmileBASIC Source chat runs on websockets and has a (relatively) well defined interface that only infrequently changes. You must authenticate yourself with the server and all messages are exchanged in JSON format. ------------------------- * Typical Steps Overview As a quick overview, these are the steps you might take to connect to the websocket chat: - Send login information to [[/query/submit/login]] - Retrieve session from "result" field from output of [[/query/submit/login]] - Send session to [[/query/request/chatauth?session=yourSession]] - Retrieve chatauth token from "result" field from output of [[/query/request/chatauth?session=yourSession]] - Open a websocket connection to [[ws://]] - Send a "bind" message with your chatauth token and wait for the response - Begin parsing messageList and userList objects - Periodically send the "ping" message when you're active - Send message JSON objects when you need to send a message Please note that all "[[]]" URLs can be substituted for "[[]]" to use the development site. Please try to test on the development site until your project works so you don't bug people in chat. ------------ * Query API ALL website queries (such as login, chatauth request, etc.) use the same API. If you simply visit a query page in your browser, you will receive a JSON object that describes that particular API. The important returned fields are as follows: | ~result~ | The result of the API request (for instance, the auth token) | ~queryok~ | Whether or not the API understood your request | ~errors~ | An array of errors that occurred while processing your request | ~inputvalues~ | ANY input values the API understood | ~requester~ | Who is requesting the page (useful to see if the page accepted your session) | ~acceptedfields~ | Information on which fields the API accepts and how it accepts them (like POST/GET, the type, etc.) When you make a request to a query page, you should check to make sure that ~requester~ is set to you. If you don't see your username there, that means the page didn't accept your session token. Either pass a session cookie or pass the session in the URL with ~?session=yourSessionID~. Sessions are good for up to 1 week (unless you purposefully logout). If the requester is valid, make sure that ~queryok~ is also valid; if you send a set of inputs the API doesn't understand, this field will be false. Finally, if you want to learn more about a particular query page, ~acceptedfields~ will tell you all about what a page accepts. ----------------------------------- * Login Session and Authentication Before you connect to the chat, you must retrieve an authentication token from the website. This token identifies you when you connect to chat. To retrieve this token, you must first get a login session. You can either steal the session cookie from the browser or POST your username and password to the login API and retrieve the session that way. This guide assumes you are doing the latter. Send a POST request with ~username=yourUsername~ and ~password=md5(yourPassword)~ to the following URL (yes, your password MUST be md5 encoded before sending; this is an old requirement and your password is NOT stored in MD5 on the server): [[]] Parse the JSON output and your login session will be in the "result" field. This session is valid for up to 1 week, so you can reuse it if you want*. Now take this result and send a GET request to the following URL (it can be POST, but you're not actually sending any data this time): [[]] If this succeeds, your chat authorization token will be in the ~result~ field in the JSON output. This token is good for up to 10 minutes outside of chat, or forever while in chat**. While you're at it, you should probably pull your "uid" from the ~requester~ field as well, since you'll need that to connect to chat too. /*Performing a login will generate a new session every time. Please try to conserve sessions, as each of these can be used to impersonate you./ /**Requesting the ~chatAuth~ over and over again will yield the same token if that token has not expired yet. Don't worry about requesting multiple times/ ------------- * Connecting There are two websocket chats. While developing, please try to test on Development before moving to the main chat so you don't disturb anybody. The websocket chat URLs are: *Main*: [[ws://]] *Development*: [[ws://]] The wss protocol (websocket secure) is not supported at this time. Remember, all messages to and from chat should be JSON objects. Once you've opened the websocket, you will need to send the ~bind~ message, which tells the chat who you are. The ~bind~ message is described in the next section. Your UID should be a number, but the type and key should be strings. ~lessData~ SHOULD be set to true, unless you absolutely need all the extra data (things like user badges, a bunch of garbage, etc.). Without ~lessData~, responses will be about 10 times larger. I believe it defaults to true, but someone recently made an API and said that it didn't, so just to be safe, make sure it's set to true. All responses from the chat are also JSON, so you should wait for the response object. The objects that the chat sends are described in the "[[#header-7][Server JSON message formats]]" section ------------------------------ * Client JSON message formats ** Bind The first message you send to the server should be formatted like this: #+BEGIN_SRC json { "type": "bind", "uid": yourUID, "lessData" : true, "key": "yourChatAuth" } #+END_SRC ** Requests You can request the current messages and users from the server. The server will send the latest 20(?) messages from each room when you send a ~messageList~ request: #+BEGIN_SRC json { "type": "request", "request": "messageList or userList" } #+END_SRC Note that sending a request will get you both a ~response~ object AND the object you asked for. These will be two separate messages from the server. ** Messages When it's finally time for you to send your own message, format it like so: #+BEGIN_SRC json { "type": "message", "text": "Your message, silly!", "key": "yourChatAuthkey", "tag": "general/offtopic/admin/all/pmroom#" } #+END_SRC The chat works on a "room" system where each message has a single tag to tell it which room to go in. "general" is the "Programming" tab where we talk about programming (sometimes). "offtopic" is where most people are and is basically anything. "admin" is specifically for admins and you can't post here unless you're an admin (but you can always see messages from the admin tab). Finally, each PM room has its own unique tag. You can see which pm rooms you're in with the ~userList~ object. ** Ping To keep yourself from appearing inactive, send this ping message when you're "available". I believe that setting ~active~ to false does NOT make you appear away though: #+BEGIN_SRC json { "type": "ping", "active": true/false } #+END_SRC ------------------------------ * Server JSON message formats ** Requests Responses to requests (such as bind, etc.) are in the following format: #+BEGIN_SRC json { "type": "response", "id": uniqueMessageID, "from": "bind/messageList/userList/etc.", "result": true/false, "errors": ["error1","error2",etc.], "extras": {"somethingspecial":"whatever"} } #+END_SRC The ~extras~ field contains data specific to the request that produced it. Just inspect the field for various requests; none of the data there is actually important. For ~bind~, it sends the list of available modules in "modules". ** MessageList ~MessageList~ objects are sent automatically, or you can specifically request them yourself. For some reason, binding does NOT send out an initial ~messageList~, even though it sends out the userlist. You should perform a ~messageList~ request after binding if you want to see anything. #+BEGIN_SRC json { "type": "messageList", "id": uniqueMessageID, "messages": [ { messageObject }, { messageObject }, etc. ] } #+END_SRC ** Message Object Each message is formatted in the following way (these are the messages included in the ~messages~ array from ~messageList~): #+BEGIN_SRC json { "type": "warning/system/module/message", "id": uniqueMessageID, "tag": "general/offtopic/admin/all/pmroom#", "encoding": "image/code/raw/markdown/text/draw", "subtype": "shutdown/join/leave/welcome/warning/blocked/none", "safe": true/false, "sender": { userObject }, "recipients": [uid1,uid2,etc.], "message": "The actual message for this thing", "time": "UTC time of message" } #+END_SRC Most regular messages will be the "text" encoding type, but you should be prepared for other types. "draw" is the chatdraw format which is described in the chatDraw documentation. An image format simply means that the message consists of ONLY a link to an image; it is NOT the binary data for an image or anything like that. Likewise for code: it is just text that you MAY format as code if you wish. Markdown indicates that this message has markdown syntax and you MAY convert it if you wish, but it is once again just text. Raw may contain HTML, but is still just text. You can display this html or simply strip the tags... whatever you want. The ~subtype~ is used mainly for warning and system message types. This can be helpful if you need to know when the server shutdown, when you're blocked, etc. without having to parse the message itself to determine the meaning. ** User Object These aren't necessarily sent from the server DIRECTLY, but they're included in messages and userlists. To save typing, the information is only put here once: #+BEGIN_SRC json { "username": "TheirNameOfCourse", "stars": "User's Rank (Admin/Chat Moderator/etc.)", "level": userIntegerRank, "uid": userUID, "joined": unixJoinTime, "avatar": "full(?)linkToUserAvatar", "active": true/false, "banned": true/false } #+END_SRC ~joined~ is the unix timestamp for when the user joined. ~active~ is whether or not the user has had activity or sent a ping in a while. ** UserList THIS is the object you'll actually get from chat to describe which users are currently in chat or not. #+BEGIN_SRC json { "type": "userList", "id": uniqueMessageID, "users": [ { userObject1 }, { userObject2 }, etc. ], "rooms" [ { roomObject1 }, { roomObject2 }, etc. ] } #+END_SRC Remember, ~userObjects~ all have the format described in the previous section, including the ~userObjects~ within messages.