Compare commits

..

15 Commits

Author SHA1 Message Date
knotteye 791b96a476 Fix the 'Chat' link on user profiles 2021-02-21 14:29:25 -06:00
knotteye 9800012ba0 Fix a bug where clicking the watch link on a profile would disconnect the user from the chat. 2021-02-21 13:23:43 -06:00
knotteye 53aa632da4 Properly decrement viewer count when RTMP viewers quit 2021-02-21 13:16:33 -06:00
knotteye 90f3c31ffb Fix a bug where chat integration code could try to access an array before it was initialized 2021-02-21 13:15:34 -06:00
knotteye b9d8ddaf5f Update dependencies 2021-02-20 22:11:05 -06:00
knotteye cc2fb358c9 Make user profile page a little more responsive 2021-01-16 00:02:39 -06:00
knotteye 5e662e5ca9 Merge pull request 'viewercount' (#28) from viewercount into develop
Reviewed-on: #28
2021-01-15 23:19:02 -06:00
knotteye aa111acdfb Add viewer count to web UI 2021-01-15 23:14:57 -06:00
knotteye dd940ff46f Fix bugs with updating viewer count 2021-01-15 21:28:38 -06:00
knotteye bcba160146 Update API documentation 2021-01-15 21:28:18 -06:00
knotteye 21a85fa26c Decrement viewer count when appropriate 2021-01-15 20:27:02 -06:00
knotteye f966bda4dd Return viewer count at a couple places in the API 2021-01-15 11:07:45 -06:00
knotteye 3d131980ae Add database migrate for viewer tracking 2021-01-15 11:05:01 -06:00
knotteye 1b551a5b8f Increment and reset viewer count when appropriate 2021-01-15 10:56:32 -06:00
knotteye d79eac6b57 Change modifyLinks regex to match all valid URL characters, followed by a period, followed by a-zA-Z0-9
This should catch any legal URL with a file extension on the end
2021-01-15 09:43:16 -06:00
12 changed files with 165 additions and 152 deletions

View File

@ -55,7 +55,7 @@ play from: rtmp://example.com/live/username or https://example.com/live/username
## /api/users/live/ ## /api/users/live/
Returns the usernames and stream titles of 10 users who are currently streaming Returns the usernames and stream titles of 10 users who are currently streaming, and how many viewers they have.
**Method**: POST **Method**: POST
@ -80,7 +80,7 @@ The array will be wrapped in a JSON object under the key 'users'.
Same as above, but returns all users regardless of whether they are streaming and if they're streaming or not. Also returns a value 'live' indicating whether a user is currently streaming. Same as above, but returns all users regardless of whether they are streaming and if they're streaming or not. Also returns a value 'live' indicating whether a user is currently streaming.
**Example**: `{users: [{username:"foo", title:"bar", live:1}] }` **Example**: `{users: [{username:"foo", title:"bar", live:1, viewers:10}] }`
@ -217,9 +217,9 @@ Get information about the specified user.
**Parameters**: user **Parameters**: user
**Response**: Returns a JSON object with available information about the user. If the user is authenticated and searching for their own information, will return all available information. Otherwise it will return only the stream title, bio, and whether the stream is live. In the case of searching for a user that does not exist, the returned object will contain only the username searched for. **Response**: Returns a JSON object with available information about the user. If the user is authenticated and searching for their own information, will return all available information. Otherwise it will return only the stream title, bio, viewers and whether the stream is live. In the case of searching for a user that does not exist, the returned object will contain only the username searched for.
**Example**: `{username: "foo", title: "bar", about: "This is an example bio", live: 0}` **Example**: `{username: "foo", title: "bar", about: "This is an example bio", live: 0, viewers: 10}`

207
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "satyr", "name": "satyr",
"version": "0.10.2", "version": "1.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -202,9 +202,9 @@
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
}, },
"base64-arraybuffer": { "base64-arraybuffer": {
"version": "0.1.5", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
}, },
"base64id": { "base64id": {
"version": "2.0.0", "version": "2.0.0",
@ -240,14 +240,6 @@
} }
} }
}, },
"better-assert": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
"integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
"requires": {
"callsite": "1.0.0"
}
},
"bignumber.js": { "bignumber.js": {
"version": "7.2.1", "version": "7.2.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz",
@ -334,11 +326,6 @@
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
}, },
"callsite": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
},
"caseless": { "caseless": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@ -411,6 +398,11 @@
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
"integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
}, },
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"component-inherit": { "component-inherit": {
"version": "0.0.3", "version": "0.0.3",
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
@ -653,22 +645,22 @@
} }
}, },
"engine.io": { "engine.io": {
"version": "3.4.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.0.tgz", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz",
"integrity": "sha512-XCyYVWzcHnK5cMz7G4VTu2W7zJS7SM1QkcelghyIk/FmobWBtXE7fwhBusEKvCSqc3bMh8fNFMlUkCKTFRxH2w==", "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==",
"requires": { "requires": {
"accepts": "~1.3.4", "accepts": "~1.3.4",
"base64id": "2.0.0", "base64id": "2.0.0",
"cookie": "0.3.1", "cookie": "~0.4.1",
"debug": "~4.1.0", "debug": "~4.1.0",
"engine.io-parser": "~2.2.0", "engine.io-parser": "~2.2.0",
"ws": "^7.1.2" "ws": "~7.4.2"
}, },
"dependencies": { "dependencies": {
"cookie": { "cookie": {
"version": "0.3.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
}, },
"debug": { "debug": {
"version": "4.1.1", "version": "4.1.1",
@ -679,64 +671,58 @@
} }
}, },
"ws": { "ws": {
"version": "7.1.2", "version": "7.4.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz",
"integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==", "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA=="
"requires": {
"async-limiter": "^1.0.0"
}
} }
} }
}, },
"engine.io-client": { "engine.io-client": {
"version": "3.4.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.0.tgz", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.0.tgz",
"integrity": "sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==", "integrity": "sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==",
"requires": { "requires": {
"component-emitter": "1.2.1", "component-emitter": "~1.3.0",
"component-inherit": "0.0.3", "component-inherit": "0.0.3",
"debug": "~4.1.0", "debug": "~3.1.0",
"engine.io-parser": "~2.2.0", "engine.io-parser": "~2.2.0",
"has-cors": "1.1.0", "has-cors": "1.1.0",
"indexof": "0.0.1", "indexof": "0.0.1",
"parseqs": "0.0.5", "parseqs": "0.0.6",
"parseuri": "0.0.5", "parseuri": "0.0.6",
"ws": "~6.1.0", "ws": "~7.4.2",
"xmlhttprequest-ssl": "~1.5.4", "xmlhttprequest-ssl": "~1.5.4",
"yeast": "0.1.2" "yeast": "0.1.2"
}, },
"dependencies": { "dependencies": {
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"debug": { "debug": {
"version": "4.1.1", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": { "requires": {
"ms": "^2.1.1" "ms": "2.0.0"
} }
}, },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"ws": { "ws": {
"version": "6.1.4", "version": "7.4.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz",
"integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA=="
"requires": {
"async-limiter": "~1.0.0"
}
} }
} }
}, },
"engine.io-parser": { "engine.io-parser": {
"version": "2.2.0", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz",
"integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==",
"requires": { "requires": {
"after": "0.8.2", "after": "0.8.2",
"arraybuffer.slice": "~0.0.7", "arraybuffer.slice": "~0.0.7",
"base64-arraybuffer": "0.1.5", "base64-arraybuffer": "0.1.4",
"blob": "0.0.5", "blob": "0.0.5",
"has-binary2": "~1.0.2" "has-binary2": "~1.0.2"
} }
@ -1605,11 +1591,6 @@
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
}, },
"object-component": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
"integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE="
},
"on-finished": { "on-finished": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@ -1656,20 +1637,14 @@
"integrity": "sha512-tLfs2QiziUPFTA4nNrv2rrC0CnHDIF2o2m5TCgNss/E0asI0ltVjBcNKhcd/8vteZa8xKV5RGfD0ZFFlECMCqQ==" "integrity": "sha512-tLfs2QiziUPFTA4nNrv2rrC0CnHDIF2o2m5TCgNss/E0asI0ltVjBcNKhcd/8vteZa8xKV5RGfD0ZFFlECMCqQ=="
}, },
"parseqs": { "parseqs": {
"version": "0.0.5", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
"requires": {
"better-assert": "~1.0.0"
}
}, },
"parseuri": { "parseuri": {
"version": "0.0.5", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
"integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
"requires": {
"better-assert": "~1.0.0"
}
}, },
"parseurl": { "parseurl": {
"version": "1.3.3", "version": "1.3.3",
@ -2025,15 +2000,15 @@
} }
}, },
"socket.io": { "socket.io": {
"version": "2.3.0", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz",
"integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==",
"requires": { "requires": {
"debug": "~4.1.0", "debug": "~4.1.0",
"engine.io": "~3.4.0", "engine.io": "~3.5.0",
"has-binary2": "~1.0.2", "has-binary2": "~1.0.2",
"socket.io-adapter": "~1.1.0", "socket.io-adapter": "~1.1.0",
"socket.io-client": "2.3.0", "socket.io-client": "2.4.0",
"socket.io-parser": "~3.4.0" "socket.io-parser": "~3.4.0"
}, },
"dependencies": { "dependencies": {
@ -2048,42 +2023,34 @@
} }
}, },
"socket.io-adapter": { "socket.io-adapter": {
"version": "1.1.1", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
"integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g=="
}, },
"socket.io-client": { "socket.io-client": {
"version": "2.3.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz",
"integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==",
"requires": { "requires": {
"backo2": "1.0.2", "backo2": "1.0.2",
"base64-arraybuffer": "0.1.5",
"component-bind": "1.0.0", "component-bind": "1.0.0",
"component-emitter": "1.2.1", "component-emitter": "~1.3.0",
"debug": "~4.1.0", "debug": "~3.1.0",
"engine.io-client": "~3.4.0", "engine.io-client": "~3.5.0",
"has-binary2": "~1.0.2", "has-binary2": "~1.0.2",
"has-cors": "1.1.0",
"indexof": "0.0.1", "indexof": "0.0.1",
"object-component": "0.0.3", "parseqs": "0.0.6",
"parseqs": "0.0.5", "parseuri": "0.0.6",
"parseuri": "0.0.5",
"socket.io-parser": "~3.3.0", "socket.io-parser": "~3.3.0",
"to-array": "0.1.4" "to-array": "0.1.4"
}, },
"dependencies": { "dependencies": {
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"debug": { "debug": {
"version": "4.1.1", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": { "requires": {
"ms": "^2.1.1" "ms": "2.0.0"
} }
}, },
"isarray": { "isarray": {
@ -2091,37 +2058,27 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
}, },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"socket.io-parser": { "socket.io-parser": {
"version": "3.3.0", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz",
"integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==",
"requires": { "requires": {
"component-emitter": "1.2.1", "component-emitter": "~1.3.0",
"debug": "~3.1.0", "debug": "~3.1.0",
"isarray": "2.0.1" "isarray": "2.0.1"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
} }
} }
} }
}, },
"socket.io-parser": { "socket.io-parser": {
"version": "3.4.0", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.0.tgz", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz",
"integrity": "sha512-/G/VOI+3DBp0+DJKW4KesGnQkQPFmUCbA/oO2QGT6CWxU7hLGWqU3tyuzeSK/dqcyeHsQg1vTe9jiZI8GU9SCQ==", "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==",
"requires": { "requires": {
"component-emitter": "1.2.1", "component-emitter": "1.2.1",
"debug": "~4.1.0", "debug": "~4.1.0",

View File

@ -34,13 +34,13 @@
"nunjucks": "^3.2.1", "nunjucks": "^3.2.1",
"parse-yaml": "^0.1.0", "parse-yaml": "^0.1.0",
"recursive-readdir": "^2.2.2", "recursive-readdir": "^2.2.2",
"shaka-player": "^3.0.5",
"simple-xmpp": "^1.3.1", "simple-xmpp": "^1.3.1",
"socket-anti-spam": "^2.0.0", "socket-anti-spam": "^2.0.0",
"socket.io": "^2.3.0", "socket.io": "^2.4.1",
"strftime": "^0.10.0", "strftime": "^0.10.0",
"ts-node": "^8.5.4", "ts-node": "^8.5.4",
"typescript": "^3.6.3", "typescript": "^3.6.3"
"shaka-player": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.12.67" "@types/node": "^12.12.67"

View File

@ -80,9 +80,10 @@ async function render(path, s){
else var usr = path.substring(7); else var usr = path.substring(7);
var config = JSON.parse(await makeRequest("GET", '/api/'+usr+'/config')); var config = JSON.parse(await makeRequest("GET", '/api/'+usr+'/config'));
if(!config.title){document.body.innerHTML = nunjucks.render('404.njk', context); break;} if(!config.title){document.body.innerHTML = nunjucks.render('404.njk', context); break;}
document.body.innerHTML = nunjucks.render('user.njk', Object.assign({about: config.about, title: config.title, username: config.username}, context)); document.body.innerHTML = nunjucks.render('user.njk', Object.assign({about: config.about, title: config.title, username: config.username, viewers: config.viewers}, context));
modifyLinks(); modifyLinks();
initPlayer(usr); initPlayer(usr);
updateViewers();
break; break;
case (path.match(/^\/vods\/.+\/manage\/?$/) || {}).input: // /vods/:user/manage case (path.match(/^\/vods\/.+\/manage\/?$/) || {}).input: // /vods/:user/manage
var usr = path.substring(6, (path.length - 7)); var usr = path.substring(6, (path.length - 7));
@ -170,13 +171,25 @@ function handleLoad() {
function modifyLinks() { function modifyLinks() {
for (var ls = document.links, numLinks = ls.length, i=0; i<numLinks; i++){ for (var ls = document.links, numLinks = ls.length, i=0; i<numLinks; i++){
if(ls[i].href.indexOf(location.protocol+'//'+location.host) !== -1 && ls[i].href.match(new RegExp(/\/\w+\.\w+$/)) === null) { if(ls[i].href.indexOf(location.protocol+'//'+location.host) !== -1 && ls[i].href.match(new RegExp(/\/[A-Za-z0-9_\-~]+\.[A-Za-z0-9]+$/)) === null && ls[i].href.match(new RegExp(/\/chat\?room=.+$/)) === null) {
//should be a regular link //should be a regular link
ls[i].setAttribute('onclick', 'return internalLink(\"'+ls[i].href.substring((location.protocol+'//'+location.host).length)+'\")'); ls[i].setAttribute('onclick', 'return internalLink(\"'+ls[i].href.substring((location.protocol+'//'+location.host).length)+'\")');
} }
} }
} }
async function updateViewers(){
let vc = document.getElementById('viewercount');
if(!vc) return false;
let path = window.location.pathname;
if(path.substring(path.length - 1).indexOf('/') !== -1)
var usr = path.substring(7, path.length - 1);
else var usr = path.substring(7);
let viewers = JSON.parse(await makeRequest("GET", "/api/"+usr+"/config")).viewers;
vc.innerHTML = "[Viewers: "+viewers+"]";
setTimeout(updateViewers, 2000);
}
function internalLink(path){ function internalLink(path){
this.render(path); this.render(path);
return false; return false;
@ -231,4 +244,9 @@ function onErrorEvent(event) {
function onError(error) { function onError(error) {
// Log the error. // Log the error.
console.error('Error code', error.code, 'object', error); console.error('Error code', error.code, 'object', error);
} }
function newPopup(url) {
popupWindow = window.open(
url,'popUpWindow','height=700,width=450,scrollbars=yes,toolbar=no,menubar=no,location=no,directories=no,status=yes')
}

View File

@ -36,7 +36,7 @@ a {
} }
#jscontainer { #jscontainer {
display: flex; display: table;
justify-content: center; justify-content: center;
flex-wrap: nowrap; flex-wrap: nowrap;
flex-direction: row; flex-direction: row;
@ -46,7 +46,7 @@ a {
margin: 0px; margin: 0px;
} }
#jschild { #jschild {
display: inline; display: table-cell;
margin: 0; margin: 0;
} }

View File

@ -86,7 +86,7 @@ async function getConfig(username: string, all?: boolean): Promise<object>{
if(all) { if(all) {
let users = await db.query('SELECT stream_key,record_flag FROM users WHERE username='+db.raw.escape(username)); let users = await db.query('SELECT stream_key,record_flag FROM users WHERE username='+db.raw.escape(username));
if(users[0]) Object.assign(t, users[0]); if(users[0]) Object.assign(t, users[0]);
let usermeta = await db.query('SELECT title,about,live FROM user_meta WHERE username='+db.raw.escape(username)); let usermeta = await db.query('SELECT title,about,live,viewers FROM user_meta WHERE username='+db.raw.escape(username));
if(usermeta[0]) Object.assign(t, usermeta[0]); if(usermeta[0]) Object.assign(t, usermeta[0]);
let ci = await db.query('SELECT irc,xmpp,twitch,discord FROM chat_integration WHERE username='+db.raw.escape(username)); let ci = await db.query('SELECT irc,xmpp,twitch,discord FROM chat_integration WHERE username='+db.raw.escape(username));
if(ci[0]) Object.assign(t, ci[0]); if(ci[0]) Object.assign(t, ci[0]);
@ -94,7 +94,7 @@ async function getConfig(username: string, all?: boolean): Promise<object>{
if(tw[0]) t['twitch_mirror'] = Object.assign({}, tw[0]); if(tw[0]) t['twitch_mirror'] = Object.assign({}, tw[0]);
} }
else { else {
let um = await db.query('SELECT title,about,live FROM user_meta WHERE username='+db.raw.escape(username)); let um = await db.query('SELECT title,about,live,viewers FROM user_meta WHERE username='+db.raw.escape(username));
if(um[0]) Object.assign(t, um[0]); if(um[0]) Object.assign(t, um[0]);
} }
return t; return t;

View File

@ -13,8 +13,8 @@ var xmppJoined: Array<string> = [];
var twitchClient; var twitchClient;
var twitchArr: Array<string> = []; var twitchArr: Array<string> = [];
var discordClient; var discordClient;
var liveUsers: Array<any>; var liveUsers: Array<any> = [];
var chatIntegration: Array<any>; var chatIntegration: Array<any> = [];
async function init() { async function init() {
setInterval(updateUsers, 20000); setInterval(updateUsers, 20000);

View File

@ -27,7 +27,8 @@ async function init() {
//If satyr is restarted in the middle of a stream //If satyr is restarted in the middle of a stream
//it causes problems //it causes problems
//Live flags in the database stay live //Live flags in the database stay live
await db.query('update user_meta set live=false'); await db.query('update user_meta set live=false');
await db.query('update user_meta set viewers=0');
} }
async function bringUpToDate(): Promise<void>{ async function bringUpToDate(): Promise<void>{

8
src/db/4.ts Normal file
View File

@ -0,0 +1,8 @@
import * as db from "../database";
async function run () {
await db.query('ALTER TABLE user_meta ADD viewers int UNSIGNED DEFAULT 0 AFTER live');
await db.query('INSERT INTO db_meta (version) VALUES (4)');
}
export { run }

View File

@ -187,7 +187,7 @@ async function initAPI() {
}); });
}); });
app.post('/api/users/live', (req, res) => { app.post('/api/users/live', (req, res) => {
let qs = 'SELECT username,title FROM user_meta WHERE live=1'; let qs = 'SELECT username,title,viewers FROM user_meta WHERE live=1';
if(req.body.sort) { if(req.body.sort) {
switch (req.body.sort) { switch (req.body.sort) {
@ -217,7 +217,7 @@ async function initAPI() {
}); });
}); });
app.post('/api/users/all', (req, res) => { app.post('/api/users/all', (req, res) => {
let qs = 'SELECT username,title,live FROM user_meta'; let qs = 'SELECT username,title,live,viewers FROM user_meta';
if(req.body.sort) { if(req.body.sort) {
switch (req.body.sort) { switch (req.body.sort) {
@ -476,7 +476,7 @@ async function initSite(openReg) {
}); });
}); });
app.get('/users/:user', (req, res) => { app.get('/users/:user', (req, res) => {
db.query('select username,title,about from user_meta where username='+db.raw.escape(req.params.user)).then((result) => { db.query('select username,title,about,viewers from user_meta where username='+db.raw.escape(req.params.user)).then((result) => {
if(result[0]){ if(result[0]){
if(tryDecode(req.cookies.Authorization)) { if(tryDecode(req.cookies.Authorization)) {
res.render('user.njk', Object.assign(result[0], {auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf)); res.render('user.njk', Object.assign(result[0], {auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
@ -602,6 +602,7 @@ async function initChat() {
} }
socket.join(data); socket.join(data);
io.to(data).emit('JOINED', {nick: socket.nick, room: data}); io.to(data).emit('JOINED', {nick: socket.nick, room: data});
db.query('update user_meta set viewers = viewers + 1 where username='+db.raw.escape(data));
} }
else socket.emit('ALERT', 'Room does not exist'); else socket.emit('ALERT', 'Room does not exist');
}); });
@ -622,12 +623,14 @@ async function initChat() {
}); });
socket.on('LEAVEROOM', (data) => { socket.on('LEAVEROOM', (data) => {
io.to(data).emit('LEFT', {nick: socket.nick, room: data}); io.to(data).emit('LEFT', {nick: socket.nick, room: data});
db.query('update user_meta set viewers = viewers - 1 where username='+db.raw.escape(data));
socket.leave(data); socket.leave(data);
}); });
socket.on('disconnecting', (reason) => { socket.on('disconnecting', (reason) => {
let rooms = Object.keys(socket.rooms); let rooms = Object.keys(socket.rooms);
for(let i=1;i<rooms.length;i++){ for(let i=1;i<rooms.length;i++){
io.to(rooms[i]).emit('ALERT', socket.nick+' disconnected'); io.to(rooms[i]).emit('ALERT', socket.nick+' disconnected');
db.query('update user_meta set viewers = viewers - 1 where username='+db.raw.escape(rooms[i]));
} }
if(Array.isArray(store.get(socket.nick))) { if(Array.isArray(store.get(socket.nick))) {
store.set(socket.nick, store.get(socket.nick).filter(item => item !== socket.id)) store.set(socket.nick, store.get(socket.nick).filter(item => item !== socket.id))

View File

@ -119,10 +119,19 @@ function init () {
if(app === config['media']['publicEndpoint']) { if(app === config['media']['publicEndpoint']) {
if(keystore[key]){ if(keystore[key]){
session.playStreamPath = '/'+config['media']['privateEndpoint']+'/'+keystore[key]; session.playStreamPath = '/'+config['media']['privateEndpoint']+'/'+keystore[key];
// increment viewer count
db.query('update user_meta set viewers = viewers + 1 where username='+db.raw.escape(key));
return true; return true;
} }
} }
}); });
nms.on('donePlay', (id, StreamPath, args) => {
let session = nms.getSession(id);
let app: string = StreamPath.split("/")[1];
let key: string = StreamPath.split("/")[2];
// decrement viewer count
db.query('update user_meta set viewers = viewers - 1 where username=(select username from users where stream_key='+db.raw.escape(key)+' limit 1)');
});
} }
async function transCommand(user: string, key: string): Promise<string[]>{ async function transCommand(user: string, key: string): Promise<string[]>{

View File

@ -5,7 +5,6 @@
shakaPolyFilled = false; shakaPolyFilled = false;
var manifestUri = document.location.protocol+'//'+document.location.host+'/live/{{ username }}/index.mpd'; var manifestUri = document.location.protocol+'//'+document.location.host+'/live/{{ username }}/index.mpd';
async function initPlayer() { async function initPlayer() {
console.log('Trying to initialize player.');
if(!shakaPolyFilled){ if(!shakaPolyFilled){
shaka.polyfill.installAll(); shaka.polyfill.installAll();
shakaPolyFilled = true; shakaPolyFilled = true;
@ -58,19 +57,37 @@ function newPopup(url) {
popupWindow = window.open( popupWindow = window.open(
url,'popUpWindow','height=700,width=450,scrollbars=yes,toolbar=no,menubar=no,location=no,directories=no,status=yes') url,'popUpWindow','height=700,width=450,scrollbars=yes,toolbar=no,menubar=no,location=no,directories=no,status=yes')
} }
async function updateViewers(){
let vc = document.getElementById('viewercount');
if(!vc) return false;
let path = window.location.pathname;
if(path.substring(path.length - 1).indexOf('/') !== -1)
var usr = path.substring(7, path.length - 1);
else var usr = path.substring(7);
let viewers = JSON.parse(await makeRequest("GET", "/api/"+usr+"/config")).viewers;
vc.innerHTML = "[Viewers: "+viewers+"]";
setTimeout(updateViewers, 2000);
}
//function ohgodwhy(){
// document.getElementById("cursed"). attr('src', '');
// document.getElementById("cursed").attr('src', 'rtmp://{{ domain }}/live/{{ username }}');
//}
</script> </script>
</br> </br>
<span style="float: left;font-size: large;"><a href="/live/{{ username }}/index.mpd">{{ username }}</a> | {{ title | escape }}</b></span><span style="float: right;font-size: large;"> Links | <a href="rtmp://{{ domain }}/live/{{ username }}">Watch</a> <a href="JavaScript:newPopup('/chat?room={{ username }}');">Chat</a> <a href="/vods/{{ username }}">VODs</a></span> <span style="float: left;font-size: large;"><a href="/live/{{ username }}/index.mpd">{{ username }}</a> | {{ title | escape }}<span id="viewercount" onload="updateViewers">[Viewers: {{viewers}}]</span>
<script>updateViewers()</script>
</b></span><span style="float: right;font-size: large;"> Links | <a href="rtmp://{{ domain }}/live/{{ username }}" onclick="window.open('rtmp://{{ domain }}/live/{{ username }}').close(); return false;" ><!--If anyone is reading this I want you to know that that horrifying hack fixes a nasty bug that drove me temporarily insane. I'm not touching it.-->Watch</a> <a href="/chat?room={{ username }}" onclick="newPopup('/chat?room={{ username }}'); return false;">Chat</a> <a href="/vods/{{ username }}">VODs</a></span>
<div id="jscontainer"> <div id="jscontainer">
<div id="jschild" style="width: 70%;height: 100%;position: relative;"> <div id="jschild" style="width: 70%;height: 100%;position: relative;">
<image id="playbtn" src="/play.svg" alt="Play Stream" style="width:100%;height:100%;width: 950px;height: 534px;position: absolute;" onclick="document.getElementById('video').paused ? document.getElementById('video').play() : document.getElementById('video') .pause();"></image> <image id="playbtn" src="/play.svg" alt="Play Stream" style="width:100%;height:100%;height: 100%;position: absolute;" onclick="document.getElementById('video').paused ? document.getElementById('video').play() : document.getElementById('video') .pause();"></image>
<video id="video" style="width:100%;height:100%;width: 950px;height: 534px;background-color: rgb(0, 0, 0);" poster="/thumbnail.jpg" autoplay onclick="this.paused ? this.play() : this.pause();"></video> <video id="video" style="width:100%;height:100%;width: 100%;height: 100%;background-color: rgb(0, 0, 0);" poster="/thumbnail.jpg" autoplay onclick="this.paused ? this.play() : this.pause();"></video>
</div> </div>
<div id="jschild" class="webchat" style="width: 30%;height: 100%;position: relative;"> <div id="jschild" class="webchat" style="width: 30%;height: 100%;">
<iframe src="/chat?room={{ username }}" frameborder="0" style="width: 100%;height: 100%; min-height: 534px;" allowfullscreen></iframe> <iframe src="/chat?room={{ username }}" frameborder="0" style="min-width: 100%;min-height: 100%;" allowfullscreen></iframe>
</div> </div>
</div> </div>
</br> </br>
<noscript>The webclients for the stream and the chat require javascript, but feel free to use the direct links above!</br></br></noscript> <noscript>The webclients for the stream and the chat require javascript, but feel free to use the direct links above!</br></br></noscript>
{{ about | escape }} {{ about | escape }}
{% endblock %} <iframe name="cursed" frameborder="0" style="display: none;width:0;height:0;border:0;visibility: hidden;"></iframe>
{% endblock %}