diff --git a/README.md b/README.md index db204c1..1d2c4a7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ System dependencies: A stable version of node>=10, mysql3 (or a compatible imple ### Setup Instructions ```bash -git clone https://gitlab.com/knotteye/satyr.git +git clone https://pond.waldn.net/git/knotteye/satyr.git cd satyr npm install npm run setup diff --git a/docs/CHAT.md b/docs/CHAT.md index f32ecae..a3bebd7 100644 --- a/docs/CHAT.md +++ b/docs/CHAT.md @@ -3,7 +3,7 @@ This is not a guide to using the webchat, this a reference point for writing cli Satyr's webchat is based on [socket.io](https://socket.io/), you can find clients for [Java](https://github.com/socketio/socket.io-client-java), [C++](https://github.com/socketio/socket.io-client-cpp), [Swift](https://github.com/socketio/socket.io-client-swift), [Dart](https://github.com/rikulo/socket.io-client-dart), and probably more. -Socket.IO is loosely reminiscent of IRC in that you will receive events from the server and sent events to it. The following is a list of incoming and outgoing events you will need to handle or send. If you would like to see examples, templates/chat.html is implementation used in the webclient by satyr. +Socket.IO is loosely reminiscent of IRC in that you will receive events from the server and sent events to it. The following is a list of incoming and outgoing events you will need to handle or send. If you would like to see examples, templates/chat.html is the implementation used in the webclient by satyr. # Incoming Events These are events you will recieve from the server that need to be handled in some way. @@ -53,12 +53,12 @@ This is a request to set the client's nickname. The data attached to a NICK even password: "the optional password" } ``` -During the initial connect of the client, the server will check for the "Authorization" cookie. If the cookie is a valid, signed JWT, the client will be assigned the nickname of the user that cookie belongs to. If it doesn't exist or is invalid, the client will be assigned a nickname of the form Guest+some integer. +During the initial connect of the client, the server will check for the "Authorization" cookie. If the cookie is a valid, signed JWT the client will be assigned the nickname of the user that cookie belongs to. If it doesn't exist or is invalid, the client will be assigned a nickname of the form Guest+some integer. The server will send an alert notifying the client of either the nickname change, or some error. ## MSG -This is a chat message to send to room. It should be a JSON object in the following format: +This is a chat message to send to a room. It should be a JSON object in the following format: ``` { room: "the room to send the messag to", @@ -117,10 +117,10 @@ A request to unban an IP address. It can only be done by the owner of the room. # Final Notes -Sending more than 10 messages a second will cause the server to kick your client. If kicked this way 3 times, the client will be banned for 20 minutes. +Sending more than 10 messages per second will cause the server to kick your client. If kicked this way 3 times, the client will be banned for 20 minutes. Kicked or banned users will not be notified of this through an event. -The server *will* send your own MSG events back to you, you will need to parse them out if you want to append them immediately. +The server *will* send your own MSG events back to you, you will need to parse them out if you want to display them immediately. Clients who successfully authenticate as a registered user, through either a password or a signed JWT, can ignore nickname collision and have as many connections as they wish. \ No newline at end of file diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 2cd6bb6..0c1853a 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -7,11 +7,23 @@ Some values you might want to change are satyr: registration: true # allow new users to register + port: 8000 +# the port to serve http on http: hsts: true # enable strict transport security +rtmp: + port: 1935 +# change the port to serve rtmp on + cluster: false +# enable clustering for the RTMP server +# clustering is an attempt to take better advantage of multi threaded systems in spite of node.js being single-threaded +# satyr will spawn one RTMP Worker per CPU core, and round-robin incoming connections between workers +# If you turn this on, satyr will no longer be able to reliably serve RTMP streams to clients +# Your users will have to use DASH instead + media: record: true # allow users to record VODs @@ -36,7 +48,7 @@ transcode: # https://trac.ffmpeg.org/wiki/HWAccelIntro is a good place to start # having more than 4-5 variants will start giving diminishing returns on stream quality for cpu load -# if you can't afford to generate at least 3 variants, it's reccomended to leave adaptive streaming off +# if you can't afford to generate at least 3 variants, it's recommended to leave adaptive streaming off crypto: saltRounds: 12 @@ -44,12 +56,11 @@ crypto: # if you don't understand the implications, don't change this chat: -# the following settings are for chat mirroring bots -# users will still need to choose which channel to mirror +# the following settings are for chat bridging bots +# users will still need to choose which channel to bridge # for their chat at /profile/chat irc: enabled: true -# enable irc mirroring server: chat.freenode.net port: 6697 tls: true @@ -61,7 +72,6 @@ chat: discord: enabled: true -# enabled discord integration token: abcdefghijklmnopqrstuvwxyz # the access token for the bot # note that the bot will mirror every channel matching the name the user has chosen @@ -74,6 +84,20 @@ chat: # access token for the twitch chat bot # this is not the account password, you will need to generate a token here: # https://twitchapps.com/tmi/ + + xmpp: + enabled: true + server: 'example.com' + port: 5222 + jid: 'exampleBot@example.com' + password: 'abcde' +# connection settings for the bot + nickname: 'SatyrChat +# the nickname the bot will join MUCs with + +# note that for the best experience you should set the default number of history messages to 0 for the MUC +# The bot will attempt to request 0 history messages anyway, and will also attempt to ignore any history messages it receives +# but both of these things are unreliable ``` ### Web Frontend diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 136d9a3..cf2b82a 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -4,14 +4,14 @@ A more detailed walkthrough. ### System Dependencies Install ffmpeg(>= 4.2.1) and mysql through your distribution's package manager. See [this page](https://nodejs.org/en/download/package-manager/) for instructions on installing node v10. -If the version in your distro's package manager is different, you can install 'n' through npm to manage node versions. +If the version in your distro's package manager is different, you can install [n](https://www.npmjs.com/package/n) through npm to manage node versions. ### Installing Satyr Before starting, you should create a system user to run the satyr service. Clone the repository and change to the directory ```bash -git clone https://gitlab.com/knotteye/satyr.git +git clone https://pond.waldn.net/git/knotteye/satyr.git cd satyr ``` Install nodejs dependencies diff --git a/docs/REST.md b/docs/REST.md index 9058efb..784b883 100644 --- a/docs/REST.md +++ b/docs/REST.md @@ -32,6 +32,7 @@ Configuration of the instance relating to media ``` { rtmp: { + cluster: false, port: 1935, ping_timeout: 60 }, diff --git a/docs/USAGE.md b/docs/USAGE.md index 1469cf2..0e57a77 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -1,30 +1,19 @@ ## Satyr Usage ### Administration -Satyr needs access to port 1935 for RTMP streams, and will serve HTTP on port 8000. The ports can be changed with follow config lines. +Satyr needs access to port 1935 for RTMP streams, and will serve HTTP on port 8000. See CONFIGURATION.md for details on changing this. -For HTTPS, run a reverse proxy in front of satyr. An example nginx block is shown below. -``` -server { - port 80; - port [::]80; - server_name example.tld; - return https://$server_name$request_uri 301; -} -server { - port 443 ssl; - port [::]443 ssl; - server_name example.tld; +For HTTPS, run a reverse proxy in front of satyr. An example nginx config can be found at install/satyr.nginx +An example systemd service can also be at install/satyr.service - ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem; - ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem; +#### CLI +Satyr's CLI tool can be run with `npm run cli` or `node_modules/.bin/ts-node src/cli.ts` + +It's not very complex. The following commands are available: +* `npm run cli -- --adduser sally --password "hunter12"` to create user sally with the password hunter12 +* `npm run cli -- --rmuser sally` to remove user sally +* `npm run cli -- --invite` to generate an invite code used for creating account even when registration is closed - location / { - proxy_pass http://127.0.0.1:8000/; - } -} -``` ### Users @@ -35,13 +24,14 @@ Stream keys can be changed at example.tld/changesk, and passwords at /changepwd #### Chat Chat is based on Socket.IO, and can be accessed through the webclient at /chat. Chatting and changing a nickname do not require authentication, but the usernames of streamers are reserved. + The following commands are available: -`/nick sally (password)` Password is only required if sally is a registered user. -`/join sally` Join the chatroom for sally's stream and leave the previous room. -`/kick bob` Available only in your own room if you are a streamer. Forcefully disconnect the user. -`/ban bob (time)` Ban a user from your room. Bans are based on IP address. The optional time is in minutes. The default is 30. -`/banlist` List the IPs currently banned from your room. -`/unban (ip)` self explanatory +* `/nick sally (password)` Password is only required if sally is a registered user. +* `/join sally` Join the chatroom for sally's stream and leave the previous room. +* `/kick bob` Available only in your own room if you are a streamer. Forcefully disconnect the user. +* `/ban bob (time)` Ban a user from your room. Bans are based on IP address. The optional time is in minutes. The default is 30. +* `/banlist` List the IPs currently banned from your room. +* `/unban (ip)` self explanatory You can set up mirroring to and from webchat rooms and IRC channels, twitch streams, and discord server channels. More information is in CONFIGURATION.md diff --git a/install/config.example.yml b/install/config.example.yml index 8925c03..74fc8f2 100644 --- a/install/config.example.yml +++ b/install/config.example.yml @@ -51,10 +51,11 @@ chat: xmpp: enabled: false - server: + server: port: 5222 - nickname: 'SatyrChat' - username: 'SatyrChat' + jid: + password: + nickname: twitch: enabled: false diff --git a/install/satyr.nginx b/install/satyr.nginx new file mode 100644 index 0000000..fb3dc2f --- /dev/null +++ b/install/satyr.nginx @@ -0,0 +1,25 @@ +server { + port 80; + port [::]80; + server_name example.tld; + return https://$server_name$request_uri 301; +} +server { + port 443 ssl; + port [::]443 ssl; + server_name example.tld; + + ssl_trusted_certificate /etc/letsencrypt/live/example.tld/chain.pem; + ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem; + + location / { + proxy_pass http://127.0.0.1:8000/; + } + + location ~* \.(mpd|m4s|mp4)$ { + # nginx can serve static files faster than node + # this should improve performance + root /opt/satyr/site/; + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c5e1872..e70e2b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,30 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.67.tgz", "integrity": "sha512-R48tgL2izApf+9rYNH+3RBMbRpPeW3N8f0I9HMhggeq4UXwBDqumJ14SDs4ctTMhG11pIOduZ4z3QWGOiMc9Vg==" }, + "@xmpp/jid": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@xmpp/jid/-/jid-0.0.2.tgz", + "integrity": "sha1-DVKMqdWNr8gzZlVk/+YvMyoxZ/I=" + }, + "@xmpp/streamparser": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@xmpp/streamparser/-/streamparser-0.0.6.tgz", + "integrity": "sha1-EYAz6p23yGoctGED8mnr/3n28eo=", + "requires": { + "@xmpp/xml": "^0.1.3", + "inherits": "^2.0.3", + "ltx": "^2.5.0" + } + }, + "@xmpp/xml": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@xmpp/xml/-/xml-0.1.3.tgz", + "integrity": "sha1-HxQ5nlPkGWiFWGmPbGLnHjmoam4=", + "requires": { + "inherits": "^2.0.3", + "ltx": "^2.6.2" + } + }, "a-sync-waterfall": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", @@ -46,6 +70,17 @@ "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -108,6 +143,14 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, "asn1.js": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.2.0.tgz", @@ -118,16 +161,41 @@ "minimalistic-assert": "^1.0.0" } }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" + }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, + "backoff": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.3.0.tgz", + "integrity": "sha1-7nx+OAk/kuRyhZ22NedlJFT8Ieo=" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -157,6 +225,21 @@ "node-pre-gyp": "0.15.0" } }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + } + } + }, "better-assert": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", @@ -236,6 +319,11 @@ "fill-range": "^7.0.1" } }, + "browser-request": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/browser-request/-/browser-request-0.3.3.tgz", + "integrity": "sha1-ns5bWsqJopkyJC4Yv5M975h2zBc=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -251,6 +339,11 @@ "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -300,6 +393,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", @@ -390,6 +491,14 @@ "ts-toolbelt": "^6.9.0" } }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", @@ -431,6 +540,11 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -506,6 +620,15 @@ } } }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -685,6 +808,26 @@ } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -728,6 +871,21 @@ "resolved": "https://registry.npmjs.org/flags/-/flags-0.1.3.tgz", "integrity": "sha1-lh0vyM3zZp1jBB4w5bJK2tNvV1g=" }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -772,6 +930,14 @@ "wide-align": "^1.1.0" } }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -794,6 +960,20 @@ "is-glob": "^4.0.1" } }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, "has-binary2": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", @@ -824,6 +1004,33 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -843,6 +1050,16 @@ } } }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, "iconv": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/iconv/-/iconv-2.2.3.tgz", @@ -955,11 +1172,21 @@ "resolved": "https://registry.npmjs.org/is-port-available/-/is-port-available-0.1.5.tgz", "integrity": "sha512-/r7UZAQtfgDFdhxzM71jG0mkC4oSRA513cImMILdRe/+UOIe0Se/D/Z7XCua4AFg5k4Zt3ALMGaC1W3FzlrR2w==" }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, "jose": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/jose/-/jose-1.15.1.tgz", @@ -968,11 +1195,47 @@ "asn1.js": "^5.2.0" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, "lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -988,6 +1251,14 @@ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, + "ltx": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/ltx/-/ltx-2.10.0.tgz", + "integrity": "sha512-RB4zR6Mrp/0wTNS9WxMvpgfht/7u/8QAC9DpPD19opL/4OASPa28uoliFqeDkLUU8pQ4aeAfATBZmz1aSAHkMw==", + "requires": { + "inherits": "^2.0.4" + } + }, "make-error": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", @@ -1001,6 +1272,16 @@ "make-error": "^1.3.5" } }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1171,6 +1452,79 @@ "tar": "^4.4.2" } }, + "node-xmpp-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/node-xmpp-client/-/node-xmpp-client-3.2.0.tgz", + "integrity": "sha1-r0Un3wzFq9JpDLohOcwezcgeoYk=", + "requires": { + "browser-request": "^0.3.3", + "debug": "^2.2.0", + "md5.js": "^1.3.3", + "minimist": "^1.2.0", + "node-xmpp-core": "^5.0.9", + "request": "^2.65.0", + "ws": "^1.1.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "requires": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } + } + } + }, + "node-xmpp-core": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/node-xmpp-core/-/node-xmpp-core-5.0.9.tgz", + "integrity": "sha1-XCjCjtsfs/i+uixnYHd2E/SPNCo=", + "requires": { + "@xmpp/jid": "^0.0.2", + "@xmpp/streamparser": "^0.0.6", + "@xmpp/xml": "^0.1.3", + "debug": "^2.2.0", + "inherits": "^2.0.1", + "lodash.assign": "^4.0.0", + "node-xmpp-tls-connect": "^1.0.1", + "reconnect-core": "https://github.com/dodo/reconnect-core/tarball/merged" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "node-xmpp-tls-connect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/node-xmpp-tls-connect/-/node-xmpp-tls-connect-1.0.1.tgz", + "integrity": "sha1-kazkOsJrE4hhsr5HjfnfGdYdxcM=" + }, "nopt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", @@ -1236,6 +1590,11 @@ "commander": "^3.0.2" } }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1262,6 +1621,11 @@ "wrappy": "1" } }, + "options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -1317,6 +1681,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -1342,6 +1711,21 @@ "ipaddr.js": "1.9.0" } }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/qbox/-/qbox-0.1.7.tgz", + "integrity": "sha1-6A8NxdCfhp2IghaMP2asjdKEDwI=" + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -1418,6 +1802,13 @@ "picomatch": "^2.2.1" } }, + "reconnect-core": { + "version": "https://github.com/dodo/reconnect-core/tarball/merged", + "integrity": "sha512-wZK/v5ZaNaSUs2Wnwh2YSX/Jqv6bQHKNEwojdzV11tByKziR9ikOssf5tvUhx+8/oCBz6AakOFAjZuqPoiRHJQ==", + "requires": { + "backoff": "~2.3.0" + } + }, "recursive-readdir": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", @@ -1426,6 +1817,40 @@ "minimatch": "3.0.4" } }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -1564,6 +1989,15 @@ } } }, + "simple-xmpp": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/simple-xmpp/-/simple-xmpp-1.3.1.tgz", + "integrity": "sha512-o0wGVlI8Q4o0qTz6Kylbo1QPOMVn+DA/vyHHZecqcQ+LK4ZWGe3wtRON9QjHAkSyxB36PoagmiUz4pHADau8Mw==", + "requires": { + "node-xmpp-client": "^3.0.0", + "qbox": "0.1.x" + } + }, "snekfetch": { "version": "3.6.4", "resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz", @@ -1742,6 +2176,29 @@ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + } + } + }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -1829,6 +2286,15 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, "ts-node": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.4.tgz", @@ -1846,6 +2312,14 @@ "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.9.9.tgz", "integrity": "sha512-5a8k6qfbrL54N4Dw+i7M6kldrbjgDWb5Vit8DnT+gwThhvqMg8KtxLE5Vmnft+geIgaSOfNJyAcnmmlflS+Vdg==" }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", @@ -1865,11 +2339,24 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==" }, + "ultron": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "requires": { + "punycode": "^2.1.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1880,11 +2367,26 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", diff --git a/package.json b/package.json index bee4cbf..5441c89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "satyr", - "version": "0.10.0", + "version": "0.10.1", "description": "A livestreaming server.", "license": "AGPL-3.0", "author": "knotteye", @@ -33,6 +33,7 @@ "nunjucks": "^3.2.1", "parse-yaml": "^0.1.0", "recursive-readdir": "^2.2.2", + "simple-xmpp": "^1.3.1", "socket-anti-spam": "^2.0.0", "socket.io": "^2.3.0", "strftime": "^0.10.0", diff --git a/site/index.html b/site/index.html index cd6342b..d3c0047 100644 --- a/site/index.html +++ b/site/index.html @@ -3,12 +3,13 @@ + + + - {% block head %} {% endblock %} diff --git a/templates/user.njk b/templates/user.njk index d00c82e..2a637ff 100644 --- a/templates/user.njk +++ b/templates/user.njk @@ -4,12 +4,43 @@ --> {% endblock %} {% block content %} @@ -28,7 +59,7 @@ function newPopup(url) { - +