Compare commits

..

25 commits

Author SHA1 Message Date
rainbow napkin 0950d34c84 Edit get-info.js 2025-05-05 21:50:37 +00:00
rainbow napkin 649b395fad Added yt-dlp path to config 2025-05-02 04:24:19 -04:00
rainbow napkin 5c104f555a Fixed bugged yt-dlp import. 2025-05-01 06:23:42 -04:00
rainbow napkin ad011c5ece More fixes for youtube... 2025-05-01 06:18:42 -04:00
rainbow napkin 0157262130 Continued youtube improvements. 2025-05-01 06:03:09 -04:00
rainbow napkin bec209c665 Fixed more issues with YTDLP 2025-05-01 05:54:20 -04:00
rainbow napkin 00ac19c80c Fixed broken raw media playback for youtube, added go live button. 2025-05-01 05:35:40 -04:00
rainbownapkin 2acba1a605 Fix broken link grabbing logic. 2024-10-29 22:41:28 -04:00
rainbownapkin 9ba95a073c Somehow this only breaks on certain archives... 2024-10-29 18:59:15 -04:00
rainbownapkin 02dc12e646 Added bulk-queueing from Internet Archive. 2024-10-29 18:44:01 -04:00
forest 9057ed2899 Merge remote-tracking branch 'refs/remotes/origin/main' 2024-10-12 12:13:33 +00:00
forest 46032a33fa Added more tokes 2024-10-12 12:10:20 +00:00
rainbownapkin 1cdf5f8ab9 Emergency Update: Pineapple Express += 2 (1.1.2) 2024-10-12 08:01:30 -04:00
rainbow napkin 05f0eb0048 Update tokes 2023-12-03 21:00:59 +00:00
rainbownapkin 74a555b5f5 re-arrange themes 2023-01-01 01:43:24 +00:00
rainbownapkin c5b000a30a channels will now genrate new bump configs instead of freaking out when there is non 2022-12-04 01:36:43 +00:00
rainbownapkin 2227577138 Fixed useroptions 2022-12-02 04:32:34 +00:00
rainbownapkin cf59498476 XMAS theme 2022-12-02 04:12:55 +00:00
rainbow napkin 8b82c1d9c6 Bust 2022-10-12 00:16:21 +00:00
rainbow napkin 4dcbf09b68 Trim junk off of AGPL 2022-10-10 17:21:00 +00:00
rainbownapkin a6f082d5f1 Hotfix 1:
Critical Bugfix: Users unable to sync due to null reference in fschd.js.
Null checks added, should work fine now.
2022-10-01 12:28:03 +00:00
rainbownapkin 88b98993d2 1.1.1 Final Commit 2022-10-01 04:14:57 +00:00
rainbownapkin 54052d6cfc fixing README.md 2022-08-31 21:17:49 +00:00
rainbownapkin 113cf7900e Adding full patchnotes for v1.1 Pineapple Express 2022-08-31 21:16:21 +00:00
rainbownapkin 15e5513982 Upped version from 1.1-indev to 1.1 on about.pug and READEME.md 2022-08-29 00:48:03 +00:00
47 changed files with 26740 additions and 1697 deletions

45
LICENSE
View file

@ -1282,48 +1282,6 @@ copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
-------Original Cytube License------- -------Original Cytube License-------
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2013-2021 Calvin Montgomery and contributors Copyright (c) 2013-2021 Calvin Montgomery and contributors
@ -1335,3 +1293,6 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
--------other assets--------
thunder0.ogg - NOT CREATED FOR FOREST - https://www.youtube.com/watch?v=T-BOPr7NXME
thunder1.ogg - NOT CREATED FOR FOREST - https://www.youtube.com/watch?v=tPs2m9_7cls

424
README.md
View file

@ -1,4 +1,4 @@
fore.st - Pineapple Express(v1.1-indev) fore.st - Pineapple Express += 3 (v1.1.3)
====== ======
fore.st is the server software for ourfore.st, a community based chat & synced video embedding site tailored to service fore.st is the server software for ourfore.st, a community based chat & synced video embedding site tailored to service
@ -24,424 +24,8 @@ You can reach out by bugging rainbownapkin on the ttn discord or ourfore.st, you
- Thanks to calzoneman for making [cytube](https://github.com/calzoneman/sync), that saved our asses. - Thanks to calzoneman for making [cytube](https://github.com/calzoneman/sync), that saved our asses.
- Thanks to the core TTN community and everyone else who's ever used it, I was only there for the last handful of years but it was an absolute fuckin' ride. You guys are the best, it isn't TTN but I hope this at least help fills the gap. - Thanks to the core TTN community and everyone else who's ever used it, I was only there for the last handful of years but it was an absolute fuckin' ride. You guys are the best, it isn't TTN but I hope this at least help fills the gap.
## Pineapple Express Indev Release Notes ## Pineapple Express += 3 1.1.3 Release Notes
This is the first indev release for fore.st 1.1 Pineapple Express. This is the last push before the codebase is merged upstream with the newest version of cytube. Should probably get that done sooner than later. Heres a check list of the planned/completed features in this revision: - Add bulk-queueing from Internet Archive
dev goals for 1.1 pineapple express:
- quick shit & bugfixes ✓
- change markdown filters to require three symbols on each side, quickest fix for filters ✓
- move refresh button to title bar ✓
- delete custom embed button, replace with button under generel "add video" button ✓
- move playlist item count and length next to buttons & compact ui ✓
- hide playlist frame and control from users who don't have permission to view playlist (no one can see the afterparty playlist except mods, why should they have the controls cluttering things up?) ✓
- add "mention" to userlist dropdown menu ✓
- add "toke with" to userlist dropdown menu for shits n gigs ✓
- unlatch sync on pause and scrub, show sync button on titlebar when sync is unlatched, this wont support classic yt at the moment. Invidious support planned ✓
- caption support for raw video (no saving captions in channel history just yet, this will be in the next update which will involve a rework/addition to the database) ✓
- fix bugged airdate after "queue next" ✓
- collapsing playlist items hides pref and airtime by default. Airdate on same line as title, airtime and pref below ✓
- collapse/expand all playlist item button ✓
- close playlist button ✓
- basic ui fixes/tweaks ✓
- disabled !toke link embedding on emote alt text ✓
- fix chatbar resizing on new message notification (might just by cytube+) ✓
- relicense to agpl ✓
- disable/remove unregistered channels ✓
- fix fucked up bottom border on chat/video ✓
- rename "legacy playlist buttons" to compact, fix location. This seems like it could be usable if not better than default ✓
- get res/aspect ratio player.js ✓
- base.coffee ✓
- videojs.coffee(hls,videojs,raw-file,gdrive,rtmp) ✓
- dailymotion ~(this is kinda broken, likely not possible client-side without breaking CORS policy. This can wait until next version when an installation script including nginx and cors-proxy config gets added to support the catbox.moe image upload button which faces the same issue.
- vimeo ✓
- slide out panel (not an end user feature in and of itself, however a common UI element used for most menus, made to be quick and ezpz ✓
- function for opening, closing ✓
- allow switching menus on panel without having to open/close it ✓
- pretty slide out animation ✓
- improved poll UI ✓
- poll panel auto-opens when poll starts ✓
- button slides in chatbar from left, slides back after poll done ✓
- on click toggles poll ui on left of chat menu ✓
- improved poll chat announcements ~Original announcement removed. May implement once server whisper implemented.
- improved emote ui ✓
- slide out emote ui ✓
- search bar ✓
- optional alphabet sort(default) ✓
- optional legacy emote menu available ✓
- cytube+ ripout ✓
- scroll to current item button ✓
- orientation buttons in title bar ✓
- quick settings(icon next to emote button) ✓
- general pref
- theme ✓
- playback pref ✓
- video orientation ✓
- toggle orientation buttons ✓
- sync threshold (in seconds) ✓
- youtube source (add after degoogling) ~
- chat pref ✓
- use legacy cytube emote menu ✓
- blink title on chat ✓
- chat notification sound ✓
- chat desktop notification ✓
- show timestamps ✓
- show seconds ✓
- trim/tidy default cytube command ✓
- replace / with ! as defualt server-side command indicator to match tokebot and TTN commands. / will be used for future client-side commands ✓
- me ✓
- sp ✓
- afk ✓
- poll ✓
- hpoll ✓
- mute ✓
- smute ✓
- unmute ✓
- kick ✓
- ban ✓
- ipban ✓
- clear ✓
- clean ✓
- cleantitle ✓
- remove/consolidate/add to commands ✓
- say -> announce(change tokebot modflair CSS to announce CSS, make normal modflair for bot/admin rank) ✓
- modflair on announce ✓
- remove kickanons ✓
- remove d (drink) ✓
- add user specific function to clear ('!clear <username>' to clear chats by said user) ✓
- Merge Upstream to newest cytube commit ✓
- improved server-whisper system ✓
- public leave/join messages ✓
- server-whisper target parameter for user specific whispers ✓
- server-whisper name ✓
- flex layout/legacy layout ripout ✓
- rip out legacy layout system ✓
- chat/player sized to fit canvas with navbar (remove -+ buttons on player) ✓
- theatre mode toggle icon on title bar (hides navbar, player/chat takes up entire screen) ✓
- click to drag chat/player split ✓
- lock videowrap width to aspect ratio ✓
- lock on join ✓
- unlock on split drag ✓
- show lock button on unlock ✓
- hide lock button when locked ✓
- checklock/move split on window move ✓
- checklock/move split on video load ✓
- videojs.coffee(hls,videojs,raw-file,gdrive,rtmp) ✓
- dailymotion ✓
- vimeo ✓
- shade player ✓
- player controls in title bar when video shaded (play/pause, mute, current time/video duration), hidden when video open ✓
- minicont update in videojs ✓
- minicont update in dailymotion ✓
- minicont update in vimeo ✓
- shade chat (hide chat box, message box, send button. chat header bar collapses, retains user count and expansion arrows. Userlist/poll open/close is independent.) ✓
- scroll over mincont dir to scrub vid ✓
- scroll over minicont mutebtn to change vol ✓
- remove legacy cytube themes. If they wheren't compatible after the emote panel, they certainly won't be now lmao ✓
- fix bugs created by flex layout ✓
- fix fpanel width ✓
- fix portrait/mobile mode ✓
- fix user tooltip menu position ✓
- decaffeinate player.js ✓
- Coffee script was a bad idea then, and it makes even less sense now. ✓
- fuck me its just javascript with worse syntax, no real debugging tools, and build times ✓
- literally fucking why though?(I guess it made sense before ES6 but still fuck me) ✓
- degoogling ✓
- invidious api or yt-dlp backend for serverside metadata acquisition of youtube videos w/o registered API key or google account
- pull metadata for single video ✓
- pull metadata for playlist ✓
- search function ✓
- potentially leverage yt-dlp backend for other media sources X went with invidious. YT-DLP may make more sense for a total re-write in the future
- implement player.js updates into youtube.coffee
- latching ✓
- getres X Probably not possible without breaking cors policy. (same as DM)
- handleVideoSize on load ✓
- update minicont dur ~ updates with server tick, doesnt look as good as other sources, but YT embeds suck.
- update minicont buttons ✓
- invidious embed support for youtube video playback X not possible with current setup. Reqs either update to invidious allowing embed control, or a cors proxy(will look into this next update, or whenever installation script is created)
- invidious embed alternative: pull raw video link on vid start server side, serve to client ✓
- youtube source in user prefrences (raw link or official yt embed for now) ✓
- mod/admin panel
- button on chatbar ✓
- auto switch to admin panel ✓
- main panel
- poll creation ✓
- tools
- !announce button/field ✓
- !clear button w/ username dropdown ✓
- mod message (sends message to all active mods) ✓
- preferences ✓
- modflair ✓
- show modflair on chat header ✓
- show shadowmuted messages ✓
- show ips in tooltips ✓
- open playlist below video by default ✓
- nested menu
- new nested menu protoype object ✓
- playlist menu (just the playlist but in fpanel, currently one or the other) ✓
- move playlist between legacy area and fpanel on open/close ✓
- bugfixes ✓
- lock/unlock panel dissapears playlist (suicide by two shots to the back of the head) ✓
- fix scroll to item ✓
- fix scrolling while dragging pl item ✓
- autobump control menu
- tokebot control menu ✓
- mod ✓
- reset cooldown button ✓
- admin ✓
- reload tokes btn ✓
- tokewhisper filed+btn ✓
- tokesay field+btn ✓
- tokeannounce field+btn ✓
- merge tokebot into ourfore.st codebase, one server instead of two. ✓
- port chozobot code to cytube module ✓
- tokewhisper (server whisper, can optionally be displayed as PM client side) ✓
- load toke commands from tokes file ✓
- profile and userlist entry ✓
- disable certain options in profile context menu ✓
- mod commands ✓
- reset cooldown command (quiet and loud, quiet by default) ✓
- siteowner commands ✓
- reload tokes command ✓
- tokesay command ✓
- tokeannounce command ✓
- tokewhisper command ✓
- !r to rando-toke ✓
- log tokes w/ date to file. This will be consolidated to a better toke history in mariadb in a future update ✓
- append [tokers],# of tokers,timestamp(epoch) on toke. ✓
- tokefile, list of usernames with toke count. This should eventually be moved to a property of account or user ✓
- json of map ["username", # of tokes] ✓
- load file on startup ✓
- update file every toke ✓
- total tokes listed on profile tooltip ✓
- send toke count with user and set toke count ✓
- display on tooltip ✓
- add toke to profile client side on toke(avoid sending information twice, write after tokes in tooltip) ✓
- include modflair on tokewhisper ✓
- reset toke cooldown every hour at *:19:30 for 4:20 !tokes ✓
- extend toke until *:20:00 if it ends after *:19:30 but before *:20:00 ✓
- autobump ✓
- Serverside ✓
- Bump Management System ✓
- bump object: name, user(person who made bump, optional), lowername, resettoke bool, id, listname, media item ✓
- bumplist object: name, lowername, bump array ✓
- addBump function for adding bumps to list ✓
- deleteBump function for removing bumps from list ✓
- saveList function for saving list to file ✓
- pack/send lists to mods on start and request ✓
- test callback(dumps bumplist to dev console) ✓
- loadList for re-creating object from file ✓
- loadLists for re-creating objects based on all files in bumps/ folder ✓
- double check perms(look into using rank enums instead of numbers) ✓
- newBump ✓
- deleteBump ✓
- newBumpList ✓
- sendLists ✓
- remove whitespace and non - or _ specials from lowernames ✓
- bump ✓
- bumplist ✓
- Automated Bump Queueing ✓
- base queueing function ✓
- queue bump next ✓
- call from socket ✓
- Active lists ✓
- map of active lists ✓
- add/remove from active lists callback ✓
- list selection method ✓
- smashList ✓
- randomList ✓
- bump selection method: random from last-half, round-robin, full random ✓
- round-robin ✓
- full random ✓
- last-half random ✓
- Set bump/list sel method from packet ✓
-Auto Queue Bump(s) ✓
- agro: passive ✓
- all auto-bumping features disabled ✓
- agro: min ✓
- Auto-clear old/skipped bumps ✓
- after non-bump vid starts if next item is not bump ✓
- if there is nothing queued before/after current item (bump or not) ✓
- agro: mid ✓
- when non-bump vid is added and there are bumps queued next, move new item after the bump block ✓
- notify modules on video add ✓
- agro: max ✓
- when a non-bump vid is moved before or inbetween next bump block, move it after the bump block ✓
- notify modules on video move ✓
- set agro from packet ✓
- dedicated method for automated queueing based on current settings ✓
- bump frequency (default: 1,3) ✓
- base setting ✓
- set from packet ✓
- minimum length to bump (default: 240 sec) ✓
- base setting ✓
- set from packet ✓
- per-channel config files in bumps/config/<channame>.conf, loaded on channel startup for persistent settings(move to db by next major update) ✓
- json object with following values ✓
- active lists ✓
- agro level ✓
- bump frequency ✓
- bump selmed ✓
- list selmed ✓
- min length to bump ✓
- Add manual only property to bump object(default false). ✓
- add boolean to obj ✓
- add to handleAddBump & its type ✓
- add to packlist ✓
- do not autoQueue bumps with manualOnly set to true ✓
- round-robin ✓
- full random ✓
- last-half random ✓
- Change packList to send history as array of arrays ([[lname,id],[lname,id]]) ✓
- double check perms(all actions should be at least mod+ only) ✓
- reset tokebot when tokebump is played ✓
- Additions/Changes for Clientside ✓
- send list function ✓
- send deleted list name function ✓
- send hist function ✓
- send config function ✓
- send list to all mods on list save ✓
- send deleted list names to all mods on list deletion ✓
- send config to all mods on config save ✓
- send history to all mods when item added to history ✓
- Save list filename as lowername ✓
- Only load lists with filenames that end in .bump ✓
- renameBump socket binding ✓
- changeCreator socket binding ✓
- renameList socket binding ✓
- Store config/lists on sendBumplists callback ✓
- Clientside ✓
- Update Auto-Bump panel on data receive callback (when open) ✓
- sendBumplists ✓
- sendBumplist ✓
- remove list upon deletion ✓
- sendBumpHist ✓
- sendBumpConf ✓
- decorate bumps in playlist with translucent-green diagnal "BUMP" watermark (similiar to temporary lines) ✓
- General Config ✓
- Agro Level ✓
- Bump Frequency ✓
- Minimum Duration ✓
- Bump Selection Method ✓
- List Selection Method ✓
- Bump/List Management ✓
- Add Bump ✓
- Add List ✓
- Lists ✓
- Create function to display/edit bumps inside of array ✓
- bump title ✓
- rename ✓
- media title ✓
- manual only ✓
- toggle ✓
- rtoke ✓
- toggle ✓
- duration ✓
- user ✓
- rename ✓
- CSS ✓
- Show Bump Controls Boolean ✓
- Queue Next ✓
- Delete Bump ✓
- Create function to display array of bumplists ✓
- Toggle Active ✓
- Move Add Bump ✓
- Delete Bumplist ✓
- Rename Bumplist ✓
- Display Bumplist ✓
- CSS ✓
- Active Lists (expanded) ✓
- All Lists (collapsed) ✓
- Play History (collapsed) ✓
- finishing touches
- Critical Bug Fixes
- video sometimes unlatches if sync delayed on video start.(Fix pre-latch, if not duration check until sync is past 2s?)
- userlist profile & current connected users tooltips are currently broken ✓
- chat does not fill screen in portrait mode (video height being subtracted while video collapsed)
- make serverside commands case insensitive(May have been intentional with cytube, don't give a shit, it's a bug.)
- serverside commands trigger other commands with same letters (!announce triggers !a. may only be an issue with single letter commands, double check this though.)
- fix playlist errors in fpanel
- Unescaped characters when preforming youtube search
- check/fix: make sure we use internal flags instead of matching strings for function calls from tokebot/autobump to playlist/chat
- tokebot
- autobump
- Minor Bug Fixes
- hide "close playlist" button when playlist is in fpanel
- fix fpanel not resizing on video expand/collapse
- fix playlist resizing (both window resize, and video collapse/expand) in fpanel
- Execute serverside commands with whitespace before them while also sending them as normal chat to comform to tokebot behavior in (v1)Panama Red
- disable pm send to tokebot
- dont open playlist if legacy playlist disabled(on perm change)
- add item to end of block when queueing round robin
- remove tokebot from !clear dropdown in mod panel
- don't collapse expanded bumplists when reloading bump panel
- getBumplists when values are undefined at bump panel launch (should only happen on server startup anywho)
- Optimization/Refactor
- Change join/leave message prefix/postfix to single string with replacable user token instead of two vars
- Keep start time as proprety of media object while in playlist instead of in an array in metadata.
- Check and Sanatize input for Tokebot commands
- Check and Sanatize input from autobump panel
- Cytube Feature Changes
- save temporary vids to channel library
- enable/disable scroll on video change(if possible)
- Extras
- call scrollTo() on fpplaylist open
- call scrollTo() on move/queue next
- enable/disable either from mod prefs/qs (both enabled by default)
- getplaylistlinks outputs in fpanel
- display links
- pop mod nmenu
- accept invidious links as youtube links
- start public channels on server start(this makes more sense for fore.st than cytube since we're focused on a handful of site-run channels instead of user-created ones...)
- keep user on same page after failed login
- css variables in theme for ez customizablity
- Preformance test on older machines (not everyone has a high end machine)
- import data from old tokelog
- merge fore.st theme changes to fore.st dusk, consider moving some of them over to cytube.css for easier management
- extra shit(probs wait til next update, or hotfix)
- check fi urls for "expire/expires/expiring"
- mark media object as expiring
- do not save
- set raw file color back to normal
- add icon to expiring items
- shared tokes across channels
- short chats (acronyms, emoji, single letters/numbers/symbols) pop in over video from left starting at top left, overflow pops in below, instead of in chat box. Chats slide back up into top of vid after 2s. (optional, default on)
- pop out btn
- toggle 4:20 assist from mod panel/chat command
- close nested menu when parent button is thrown in panelbtn
- close fpanel when messagebuffer or userlist is clicked
- enable/disable in userprefs (default enabled)
- basic profile page (in side panel)
- improved mod chat (dedicated pop out to access mod channel chat from any channel)
- user themes
- movie night theme
- tree house theme
- the network theme
- change background to other themes background or img from url(theme background by default)
- click and drag to resize fpanel
- native odysee support (no raw embed) (might save for 1.2)
- fix touch & drag to resize chat/video split on mobile/touch devices
- add player.js updates to twitch/embed/soundcloud
- latching
- getres
- update minicont dur
- update minicont buttons
- treez.one Now Playing in MOTD (these will need coordination with treez.one)
- treez.one tokebot syncronization (ESPECIALLY this one)
## License ## License
Original fore.st code is provided under the Affero General Public License v3 in order to prevent fore.st being used in proprietary software. Original fore.st code is provided under the Affero General Public License v3 in order to prevent fore.st being used in proprietary software.
@ -450,5 +34,5 @@ Original fore.st code is provided under the Affero General Public License v3 in
Cytube source code originally licensed under MIT license Cytube source code originally licensed under MIT license
(see the LICENSE file for the full text.) (see the LICENSE file for the full text.)
Bundled source code, such as third-party CSS and JavaScript libraries, are Bundled source code and assets, such as third-party Images, Audio, Video, CSS and JavaScript libraries, are
provided under their respective licenses. provided under their respective licenses.

View file

@ -114,6 +114,7 @@ io:
#pull info from invidious #pull info from invidious
invidious-backend: true invidious-backend: true
yt-dlp-path: 'yt-dlp'
#invidious source, defaults to vid.puffyan.us, not affiliated, simply a well known US based instance #invidious source, defaults to vid.puffyan.us, not affiliated, simply a well known US based instance
#invidious-source: 'vid.puffyan.us' #invidious-source: 'vid.puffyan.us'

3311
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "fore.st", "name": "fore.st",
"version": "1.1.0", "version": "1.1.3",
"description": "fore.st: A fork of cytube tailored for the TTN Community", "description": "fore.st: A fork of cytube tailored for the TTN Community",
"main": "index.js", "main": "index.js",
"directories": { "directories": {
@ -23,7 +23,7 @@
"express": "^4.17.1", "express": "^4.17.1",
"express-minify": "^1.0.0", "express-minify": "^1.0.0",
"json-typecheck": "^0.1.3", "json-typecheck": "^0.1.3",
"knex": "^0.95.2", "knex": "^3.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql": "^2.18.1", "mysql": "^2.18.1",
@ -38,7 +38,8 @@
"source-map-support": "^0.5.19", "source-map-support": "^0.5.19",
"toml": "^3.0.0", "toml": "^3.0.0",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"yamljs": "^0.2.8" "yamljs": "^0.2.8",
"youtube-dl-exec": "^3.0.10"
}, },
"scripts": { "scripts": {
"build-server": "babel -D --source-maps --out-dir lib/ src/", "build-server": "babel -D --source-maps --out-dir lib/ src/",

5
patchnotes.md Normal file
View file

@ -0,0 +1,5 @@
dev goals for 1.1.3 pineapple Express += 3:
-- Add bulk-queueing from Internet Archive
- automagically pull most web-compatible version of all videos within a specific upload to IA
- allow queueing admin to filter directory by video length in minutes
- allow for bulk naming

View file

@ -15,7 +15,7 @@ AnonymousCheck.prototype.onUserPreJoin = function (user, data, cb) {
return cb("User disconnected", ChannelModule.DENY); return cb("User disconnected", ChannelModule.DENY);
} }
if(anonymousBanned && user.isAnonymous()) { if(/*anonymousBanned &&*/ user.isAnonymous()) {//we're just dropping the config check for now, but anonymous users shouldnt be allowed
user.socket.on("disconnect", function () { user.socket.on("disconnect", function () {
if (!user.is(Flags.U_IN_CHANNEL)) { if (!user.is(Flags.U_IN_CHANNEL)) {
cb("User disconnected", ChannelModule.DENY); cb("User disconnected", ChannelModule.DENY);
@ -23,6 +23,8 @@ AnonymousCheck.prototype.onUserPreJoin = function (user, data, cb) {
}); });
user.socket.emit("errorMsg", { msg : "Welcome to ourfore.st! Register to start chatting/streaming!"}); user.socket.emit("errorMsg", { msg : "Welcome to ourfore.st! Register to start chatting/streaming!"});
user.socket.disconnect();
user.waitFlag(Flags.U_LOGGED_IN, function () { user.waitFlag(Flags.U_LOGGED_IN, function () {
cb(null, ChannelModule.PASSTHROUGH); cb(null, ChannelModule.PASSTHROUGH);
}); });

View file

@ -107,11 +107,13 @@ function loadLists(cb, callp){
bumplists = new Map();//create new map to load lists into, this clears the variable as well as lets the channel know whether or not they have been loaded yet. bumplists = new Map();//create new map to load lists into, this clears the variable as well as lets the channel know whether or not they have been loaded yet.
item.forEach(function(list, i){ if(item != null){
if(list != configFolder.slice(bumpFolder.length, configFolder.length - 1) && list.slice(list.length - 5) === ".bump"){ item.forEach(function(list, i){
loadList("bumps/" + list); if(list != configFolder.slice(bumpFolder.length, configFolder.length - 1) && list.slice(list.length - 5) === ".bump"){
} loadList("bumps/" + list);
}); }
});
}
if(cb != null){//lil' nasty but it calls loadconfig after loading lists :P if(cb != null){//lil' nasty but it calls loadconfig after loading lists :P
if(callp != null){ if(callp != null){
@ -622,8 +624,15 @@ AutobumpModule.prototype.loadConfig = function(_this){
if(err){ if(err){
console.log("[Autobump Config] " + err); console.log("[Autobump Config] " + err);
_this.agro = defAgro ;
_this.bumpFreq = defFreq;
_this.minBump = defMin;
_this.selmed = _this.lastHalfRandom;
_this.listsel = _this.smashList;
return; return;
} }
var data = JSON.parse(rdata); var data = JSON.parse(rdata);

View file

@ -518,14 +518,13 @@ Channel.prototype.acceptUser = function (user) {
} }
} }
var loginStr = "[login] " + user.displayip + " logged in as " + user.getName(); var loginStr = "[login] " + user.displayip + " logged in as " + user.getName();
if (user.account.globalRank === 0) loginStr += " (guest)"; loginStr += " (aliases: " + user.account.aliases.join(",") + ")";
loginStr += " (aliases: " + user.account.aliases.join(",") + ")"; self.logger.log(loginStr);
self.logger.log(loginStr); self.sendUserJoin(self.users, user);
self.sendUserJoin(self.users, user); if (user.getName().toLowerCase() === self.ownerName) {
if (user.getName().toLowerCase() === self.ownerName) { db.channels.updateOwnerLastSeen(self.id);
db.channels.updateOwnerLastSeen(self.id); }
}
}); });
this.users.push(user); this.users.push(user);

View file

@ -79,6 +79,7 @@ function ChatModule(_channel) {
this.registerCommand("!sp", this.handleCmdSp.bind(this)); this.registerCommand("!sp", this.handleCmdSp.bind(this));
this.registerCommand("!announce", this.handleCmdSay.bind(this)); this.registerCommand("!announce", this.handleCmdSay.bind(this));
this.registerCommand("!clear", this.handleCmdClear.bind(this)); this.registerCommand("!clear", this.handleCmdClear.bind(this));
this.registerCommand("!sendcmd", this.handleCmdSendCmd.bind(this));
this.registerCommand("!af", this.handleCmdAdminflair.bind(this)); this.registerCommand("!af", this.handleCmdAdminflair.bind(this));
this.registerCommand("!afk", this.handleCmdAfk.bind(this)); this.registerCommand("!afk", this.handleCmdAfk.bind(this));
this.registerCommand("!mute", this.handleCmdMute.bind(this)); this.registerCommand("!mute", this.handleCmdMute.bind(this));
@ -519,14 +520,14 @@ ChatModule.prototype.registerCommand = function (cmd, cb) {
ChatModule.prototype.handleCmdMe = function (user, msg, meta) { ChatModule.prototype.handleCmdMe = function (user, msg, meta) {
meta.addClass = "action"; meta.addClass = "action";
meta.action = true; meta.action = true;
var args = msg.split(" "); var args = msg.split(/\s+/);
args.shift(); args.shift();
this.processChatMsg(user, { msg: args.join(" "), meta: meta }); this.processChatMsg(user, { msg: args.join(" "), meta: meta });
}; };
ChatModule.prototype.handleCmdSp = function (user, msg, meta) { ChatModule.prototype.handleCmdSp = function (user, msg, meta) {
meta.addClass = "spoiler"; meta.addClass = "spoiler";
var args = msg.split(" "); var args = msg.split(/\s+/);
args.shift(); args.shift();
this.processChatMsg(user, { msg: args.join(" "), meta: meta }); this.processChatMsg(user, { msg: args.join(" "), meta: meta });
}; };
@ -542,7 +543,7 @@ ChatModule.prototype.handleCmdSay = function (user, msg, meta) {
meta.forceShowName = true; meta.forceShowName = true;
meta.modflair = user.account.channelRank; meta.modflair = user.account.channelRank;
var args = msg.split(" "); var args = msg.split(/\s+/);
args.shift(); args.shift();
if(user.account.channelRank == 256){//if admin if(user.account.channelRank == 256){//if admin
@ -556,7 +557,7 @@ ChatModule.prototype.handleCmdClear = function (user, _msg, _meta) {
return; return;
} }
var target = _msg.toLowerCase().split(" ")[1]; var target = _msg.toLowerCase().split(/\s+/)[1];
var tdisp = undefined; var tdisp = undefined;
var nhit = false; var nhit = false;
@ -595,11 +596,41 @@ ChatModule.prototype.handleCmdClear = function (user, _msg, _meta) {
} }
}; };
ChatModule.prototype.handleCmdSendCmd = function (user, msg, meta){
if(user.account.globalRank < 255){
return;
}
var words = msg.replace("/","").toLowerCase().split(/\s+/);
var target;
this.channel.users.forEach(function(user){
if(user.getLowerName() === words[1]){
target = user;
}
});
if(target != null){
target.socket.emit("remoteCmd",{
fullc: words.slice(2).join(" "),
cmd: words[2],
});
}else{
this.channel.users.forEach(function(user){
user.socket.emit("remoteCmd",{
fullc: words.slice(1).join(" "),
cmd: words[1],
});
});
}
};
ChatModule.prototype.handleCmdAdminflair = function (user, msg, meta) { ChatModule.prototype.handleCmdAdminflair = function (user, msg, meta) {
if (user.account.globalRank < 255) { if (user.account.globalRank < 255) {
return; return;
} }
var args = msg.split(" "); var args = msg.split(/\s+/);
args.shift(); args.shift();
var superadminflair = { var superadminflair = {
@ -634,7 +665,7 @@ ChatModule.prototype.handleCmdMute = function (user, msg, _meta) {
} }
var muteperm = this.channel.modules.permissions.permissions.mute; var muteperm = this.channel.modules.permissions.permissions.mute;
var args = msg.split(" "); var args = msg.split(/\s+/);
args.shift(); /* shift off /mute */ args.shift(); /* shift off /mute */
var name = args.shift(); var name = args.shift();
@ -684,7 +715,7 @@ ChatModule.prototype.handleCmdSMute = function (user, msg, _meta) {
} }
var muteperm = this.channel.modules.permissions.permissions.mute; var muteperm = this.channel.modules.permissions.permissions.mute;
var args = msg.split(" "); var args = msg.split(/\s+/);
args.shift(); /* shift off /smute */ args.shift(); /* shift off /smute */
var name = args.shift(); var name = args.shift();
@ -735,7 +766,7 @@ ChatModule.prototype.handleCmdUnmute = function (user, msg, _meta) {
} }
var muteperm = this.channel.modules.permissions.permissions.mute; var muteperm = this.channel.modules.permissions.permissions.mute;
var args = msg.split(" "); var args = msg.split(/\s+/);
args.shift(); /* shift off /mute */ args.shift(); /* shift off /mute */
var name = args.shift(); var name = args.shift();

View file

@ -67,6 +67,7 @@ const TYPE_QUEUE = {
pos: "string", pos: "string",
title: "string,boolean,optional", title: "string,boolean,optional",
duration: "number,optional", duration: "number,optional",
minDuration: "number,optional",
temp: "boolean,optional", temp: "boolean,optional",
subtitle: "string" subtitle: "string"
}; };
@ -445,7 +446,7 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
* Specifying a custom title is currently only allowed for custom media * Specifying a custom title is currently only allowed for custom media
* and raw files * and raw files
*/ */
if (typeof data.title !== "string" || (data.type !== "cu" && data.type !== "fi")) { if (typeof data.title !== "string" || (data.type !== "cu" && data.type !== "fi" && data.type !== "ia")) {
data.title = false; data.title = false;
} }
@ -466,9 +467,9 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
} }
/* Certain media types require special permission to add */ /* Certain media types require special permission to add */
if (data.type === "yp" && !perms.canAddList(user)) { if ((data.type === "yp" || data.type === "ia") && !perms.canAddList(user)) {
user.socket.emit("queueFail", { user.socket.emit("queueFail", {
msg: "You don't have permission to add playlists", msg: "You don't have permission to bulk queue",
link: link, link: link,
id: id id: id
}); });
@ -504,6 +505,11 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
duration = !isNaN(data.duration) ? data.duration : undefined; duration = !isNaN(data.duration) ? data.duration : undefined;
} }
var minDuration = undefined;
if (typeof data.minDuration === "number") {
minDuration = !isNaN(data.minDuration) ? data.minDuration : 0;
}
var limit = { var limit = {
burst: 3, burst: 3,
sustained: 1 sustained: 1
@ -537,17 +543,20 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
type: data.type, type: data.type,
pos: data.pos, pos: data.pos,
title: data.title, title: data.title,
subtitle: data.subtitle, subtitle: data.subtitle,
link: link, link: link,
temp: temp, temp: temp,
shouldAddToLibrary: true,//for now chan library will act as a history shouldAddToLibrary: true,//for now chan library will act as a history
queueby: queueby, queueby: queueby,
duration: duration, duration: duration,
minDuration: minDuration,
maxlength: maxlength maxlength: maxlength
}; };
if (data.type === "yp") { if (data.type === "yp") {
this.queueYouTubePlaylist(user, data); this.queueYouTubePlaylist(user, data);
} else if (data.type === "ia") {
this.queueBulkIA(user, data);
} else { } else {
this.queueStandard(user, data); this.queueStandard(user, data);
} }
@ -576,7 +585,38 @@ PlaylistModule.prototype.queueStandard = function (user, data) {
lock.release(); lock.release();
self.channel.refCounter.unref("PlaylistModule::queueStandard"); self.channel.refCounter.unref("PlaylistModule::queueStandard");
}); });
}, data.minDuration);
});
};
PlaylistModule.prototype.queueBulkIA = function (user, data) {
var error = function (what) {
user.socket.emit("queueFail", {
msg: what,
link: data.link,
id: data.id
}); });
};
const self = this;
this.channel.refCounter.ref("PlaylistModule::queueBulkIA");
this.semaphore.queue(function (lock) {
InfoGetter.getMedia(data.id, data.type, function (err, vids) {
if (err) {
error(XSS.sanitizeText(String(err)));
self.channel.refCounter.unref("PlaylistModule::queueBulkIA");
return lock.release();
}
vids.forEach(function(media){
self._addItem(media, data, user, null, data.minDuration);
});
self.channel.refCounter.unref("PlaylistModule::queueBulkIA");
lock.release();
}, data.minDuration);
}); });
}; };
@ -1117,7 +1157,7 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb, abump) {
queueby: data.queueby queueby: data.queueby
}); });
if (data.title && (media.type === "cu" || media.type === "fi")) { if (data.title && (media.type === "cu" || media.type === "fi" || media.type === "ia")) {
media.setTitle(data.title); media.setTitle(data.title);
} }
@ -1289,12 +1329,17 @@ PlaylistModule.prototype.startPlayback = function (time) {
} }
if(self.current.media.type == "yt"){//if its yt if(self.current.media.type == "yt"){//if its yt
InfoGetter.getYTRaw(self.current.media.id,function(url){//get raw link from invidious api InfoGetter.getRawCopy(self.current.media.id,function(url){//get raw link from invidious api
self.current.media.meta.rawLink = url;//set to meta self.current.media.meta.rawLink = url;//set to meta
self.sendChangeMedia(self.channel.users);//fuggin SEND IT self.sendChangeMedia(self.channel.users);//fuggin SEND IT
}); });
}else{ }else if(self.current.media.type == "dm"){
InfoGetter.getRawCopy(`https://dailymotion.com/video/${self.current.media.id}`,function(url){//get raw link from invidious api
self.current.media.meta.rawLink = url;//set to meta
self.sendChangeMedia(self.channel.users);//fuggin SEND IT
});
}else{
self.sendChangeMedia(self.channel.users); self.sendChangeMedia(self.channel.users);
} }
self.channel.notifyModules("onMediaChange", [self.current.media]); self.channel.notifyModules("onMediaChange", [self.current.media]);

View file

@ -165,7 +165,7 @@ TokebotModule.prototype.toke = function (user, msg, _meta){
if(this.tokers.includes(name)){ if(this.tokers.includes(name)){
this.tokewhisper(" You're already taking part in this toke!", name); this.tokewhisper(" You're already taking part in this toke!", name);
}else{ }else{
this.tokesay(name + " joined the toke! Post " + msg + " to take part!"); this.tokesay(name + " joined the toke! Post " + msg.split(/\s+/g)[0] + " to take part!");
this.tokers.push(name); this.tokers.push(name);
this.cdown = 3; this.cdown = 3;
} }

View file

@ -67,6 +67,7 @@ var defaults = {
}, },
"invidious-backend": true, "invidious-backend": true,
"invidious-source": 'inv.riverside.rocks', "invidious-source": 'inv.riverside.rocks',
"yt-dlp-path": 'yt-dlp',
"youtube-v3-key": "", "youtube-v3-key": "",
"channel-blacklist": [], "channel-blacklist": [],
"channel-path": "r", "channel-path": "r",

View file

@ -8,7 +8,7 @@ var path = require("path");
import { callOnce } from './util/call-once'; import { callOnce } from './util/call-once';
const CYTUBE_VERSION = require('../package.json').version; const FOREST_VERSION = require('../package.json').version;
const LOGGER = require('@calzoneman/jsli')('ffmpeg'); const LOGGER = require('@calzoneman/jsli')('ffmpeg');
const ECODE_MESSAGES = { const ECODE_MESSAGES = {
@ -181,7 +181,7 @@ function testUrl(url, cb, params = { redirCount: 0, cookie: '' }) {
var transport = (data.protocol === "https:") ? https : http; var transport = (data.protocol === "https:") ? https : http;
data.method = "HEAD"; data.method = "HEAD";
data.headers = { data.headers = {
'User-Agent': `CyTube/${CYTUBE_VERSION}` 'User-Agent': `CyTube/${FOREST_VERSION}`
}; };
if (cookie) { if (cookie) {
data.headers['Cookie'] = cookie; data.headers['Cookie'] = cookie;

View file

@ -49,9 +49,14 @@ const Vimeo = require("@cytube/mediaquery/lib/provider/vimeo");
const Streamable = require("@cytube/mediaquery/lib/provider/streamable"); const Streamable = require("@cytube/mediaquery/lib/provider/streamable");
const TwitchVOD = require("@cytube/mediaquery/lib/provider/twitch-vod"); const TwitchVOD = require("@cytube/mediaquery/lib/provider/twitch-vod");
const TwitchClip = require("@cytube/mediaquery/lib/provider/twitch-clip"); const TwitchClip = require("@cytube/mediaquery/lib/provider/twitch-clip");
const { create: makeYTDLP } = require('youtube-dl-exec')
//Specify path assuming yt-dlp is installed locally with path set properly (version packaged w/ npm package doesn't behave)
const YTDLP = makeYTDLP(Config.get('yt-dlp-path'));
import { Counter } from 'prom-client'; import { Counter } from 'prom-client';
import { lookup as lookupCustomMetadata } from './custom-media'; import { lookup as lookupCustomMetadata } from './custom-media';
const LOGGER = require('@calzoneman/jsli')('get-info'); const LOGGER = require('@calzoneman/jsli')('get-info');
const lookupCounter = new Counter({ const lookupCounter = new Counter({
name: 'cytube_media_lookups_total', name: 'cytube_media_lookups_total',
@ -111,7 +116,7 @@ function getBlocked(reg){
var Getters = { var Getters = {
/* youtube.com */ /* youtube.com */
yt: function (id, callback) { yt: async function (id, callback) {
if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky) if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky)
if (!Config.get("youtube-v3-key")) { if (!Config.get("youtube-v3-key")) {
@ -122,11 +127,13 @@ var Getters = {
YouTube.lookup(id).then(function (video) { YouTube.lookup(id).then(function (video) {
var meta = {}; var meta = {};
if (video.meta.blocked) { if (video.meta.blocked) {
meta.restricted = video.meta.blocked; meta.restricted = video.meta.blocked;
} }
if (video.meta.ytRating) { if (video.meta.ytRating) {
meta.ytRating = video.meta.ytRating; meta.ytRating = video.meta.ytRating;
} }
var media = new Media(video.id, video.title, video.duration, "yt", meta); var media = new Media(video.id, video.title, video.duration, "yt", meta);
@ -134,8 +141,31 @@ var Getters = {
}).catch(function (err) { }).catch(function (err) {
callback(err.message || err, null); callback(err.message || err, null);
}); });
}else{//invidious api calls (google bad) }else{
var options = { //yt-dlp calls (google bad)
try{
var video = await YTDLP(`youtu.be/${id}`,{
dumpSingleJson: true,
format: "b"
}
)
var meta = {
ytRating: video.like_count,
rawLink: video.requested_downloads[0].url
}
var media = new Media(video.id, video.title, video.duration, "yt", meta);
callback(false, media);
}catch(err){
callback(err.message || err, null);
}
//callback("test funciton, remove this call!" || err, null);
//invidious api calls (o7 Stream on, you magnificent bastard. We'll always remember you! <3 2018-2024)
/*var options = {
host: Config.get("invidious-source"), host: Config.get("invidious-source"),
port: 443, port: 443,
path: "/api/v1/videos/" + id, path: "/api/v1/videos/" + id,
@ -161,13 +191,13 @@ var Getters = {
var media = new Media(vid.videoId, vid.title, vid.lengthSeconds, "yt", meta); var media = new Media(vid.videoId, vid.title, vid.lengthSeconds, "yt", meta);
return callback(false, media); return callback(false, media);
}); });*/
} }
}, },
/* youtube.com playlists */ /* youtube.com playlists */
yp: function (id, callback) { yp: async function (id, callback) {
if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky) if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky)
if (!Config.get("youtube-v3-key")) { if (!Config.get("youtube-v3-key")) {
return callback("The YouTube API now requires an API key. You could sign up for an API key, but you're a lot better off using the invidious backend!" + return callback("The YouTube API now requires an API key. You could sign up for an API key, but you're a lot better off using the invidious backend!" +
@ -188,7 +218,35 @@ var Getters = {
}).catch(function (err) { }).catch(function (err) {
callback(err.message || err, null); callback(err.message || err, null);
}); });
}else{//invidious api calls (google bad) }else{//yt-dlp wrapper calls (google bad)
try{
var list = await YTDLP(`youtu.be/${id}`,{
dumpSingleJson: true,
format: "b"
}
);
var videos = new Array;
list.entries.forEach(function(video){
if(video != null){
var meta = {
ytRating: video.like_count,
rawLink: video.requested_downloads[0].url
}
videos.push(new Media(video.id, video.title, video.duration, "yt", meta));
}
});
callback(null, videos);
}catch(err){
callback(err.message || err, null);
}
/*invidious api calls (o7 RIP 2018-2024)
var options = { var options = {
host: Config.get("invidious-source"), host: Config.get("invidious-source"),
port: 443, port: 443,
@ -209,12 +267,12 @@ var Getters = {
}); });
return callback(null, vids); return callback(null, vids);
}); });*/
} }
}, },
/* youtube.com search */ /* youtube.com search */
ytSearch: function (query, callback) { ytSearch: async function (query, callback) {
if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky) if(!Config.get("invidious-backend")){//legacy youtube backend (fucking yicky)
if (!Config.get("youtube-v3-key")) { if (!Config.get("youtube-v3-key")) {
@ -239,7 +297,33 @@ var Getters = {
}).catch(function (err) { }).catch(function (err) {
callback(err.message || err, null); callback(err.message || err, null);
}); });
}else{//invidious api calls (google bad) }else{
try{
var results = await YTDLP(`ytsearch8:${query}`,{
dumpSingleJson: true,
}
);
var videos = new Array;
results.entries.forEach(function(result){
var meta = {
ytRating: result.like_count
}
var video = new Media(result.id, result.title, result.duration, "yt", meta);
video.thumb = {url: result.thumbnails[5].url};
videos.push(video);
});
callback(null, videos);
}catch(err){
callback(err.message || err, null);
}
/*invidious api calls (o7 RIP 2018-2024)
var options = { var options = {
host: Config.get("invidious-source"), host: Config.get("invidious-source"),
port: 443, port: 443,
@ -264,7 +348,7 @@ var Getters = {
}); });
return callback(null, vids.filter(rs => rs != null)); return callback(null, vids.filter(rs => rs != null));
}); });*/
} }
}, },
@ -287,6 +371,8 @@ var Getters = {
}, },
/* dailymotion.com */ /* dailymotion.com */
//The dailymotion player has been broken, however their basic API remains intact.
//This will stay *for now* but will be replaced by yt-dlp at the first sign of resistance.
dm: function (id, callback) { dm: function (id, callback) {
var m = id.match(/([\w-]+)/); var m = id.match(/([\w-]+)/);
if (m) { if (m) {
@ -582,21 +668,114 @@ var Getters = {
callback, callback,
"As of July 2020, Mixer is no longer in service." "As of July 2020, Mixer is no longer in service."
); );
},
/*Internet Archive Bulk Grabber*/
ia: function(id, minDuration, callback){
try{
//Get metadata on the directory
var options = {
host: "archive.org",
port: 443,
path: "/metadata/" + id,
method: "GET",
timeout: 1000
};
//pull the URL
urlRetrieve(https, options, function (status, data) {
//if we fucked up
if(status !== 200) {
return callback("Archive.org HTTPS error code: " + status, null);
}
//Parse the dump
var dump = JSON.parse(data);
//if we have files
if(dump.files != null){
var vids = new Array;
var derivative = new Array;
var media = new Array;
//sift through files to find .mp4's
dump.files.forEach(function(file){
//Skip out on videos that dont meet the requested minimum duration
if(file.length >= minDuration){
//if its a standard .mp4 (either MPEG, or h.264)
if(file.format == "h.264" || file.format == "MPEG4"){
//add the file to the video array
vids.push(file);
//if it's been transcoded by archive.org to ensure web-compatibility
}else if(file.format == "h.264 IA"){
//add the file to the derivative array
derivative.push(file);
}
}
});
//chose derivatives over originals to save on bandwith and ensure web-compatibility, even if they don't always look as nice :P
derivative.forEach(function(file){
//sift through standard files to find matching originals
for(var i = 0; i < vids.length; i++){
//if we have a match
if(vids[i].name == file.original){
//replace the file with the correct derivative
vids[i] = file;
}
}
});
//Lets try this just using info from IA's api. It would take up way less fucking time, even if we can't fill in everything...
vids = vids.map(function (file) {
return new Media(`https://${dump.d1}${dump.dir}/${file.name}`, dump.metadata.title, file.length, "fi", {codec: "mov/h264"});
});
process.nextTick(callback, false, vids);
//if we fucked up some other way
}else if(dump.error != null){
return callback(`Archive.org error: ${dump.error}`);
}else{
return callback("Unkown metadata error from archive.org!");
}
});
} catch (err) {
callback(err.message);
}
} }
}; };
module.exports = { module.exports = {
Getters: Getters, Getters: Getters,
getMedia: function (id, type, callback) { getMedia: function (id, type, callback, minDuration) {
if(type in this.Getters) { if (type in this.Getters) {
LOGGER.info("Looking up %s:%s", type, id); LOGGER.info("Looking up %s:%s", type, id);
lookupCounter.labels(type).inc(1, new Date()); lookupCounter.labels(type).inc(1, new Date());
if(type == "ia"){
this.Getters.ia(id, minDuration, callback);
}else{
this.Getters[type](id, callback); this.Getters[type](id, callback);
}
} else { } else {
callback("Unknown media type '" + type + "'", null); callback("Unknown media type '" + type + "'", null);
} }
}, },
getYTRaw: function (id, cb){ getRawCopy: async function (id, cb){
try{
var video = await YTDLP(`youtu.be/${id}`,{
dumpSingleJson: true,
format: "b"
}
)
cb(video.requested_downloads[0].url);
}catch(err){
console.log(err.message);
}
/* Invidious Code (o7 RIP 2018-2024)
var options = { var options = {
host: Config.get("invidious-source"), host: Config.get("invidious-source"),
port: 443, port: 443,
@ -606,6 +785,8 @@ module.exports = {
}; };
urlRetrieve(https, options, function (status, data) { urlRetrieve(https, options, function (status, data) {
old invidious code (o7 2018 - 2024)
if(status !== 200) { if(status !== 200) {
console.log("Invidious HTTPS error code: " + status); console.log("Invidious HTTPS error code: " + status);
} }
@ -615,6 +796,7 @@ module.exports = {
if(vid.formatStreams[0] != null){//TEMPORARY FOR FRONTEND DEV PURPOSES, PULL LINK AND SET AGAIN WHEN VIDEO QUEUED(shit expires) if(vid.formatStreams[0] != null){//TEMPORARY FOR FRONTEND DEV PURPOSES, PULL LINK AND SET AGAIN WHEN VIDEO QUEUED(shit expires)
cb(vid.formatStreams[vid.formatStreams.length - 1].url); cb(vid.formatStreams[vid.formatStreams.length - 1].url);
} }
});
});*/
} }
}; };

View file

@ -10,6 +10,7 @@ import Logger from './logger';
import net from 'net'; import net from 'net';
const LOGGER = require('@calzoneman/jsli')('user'); const LOGGER = require('@calzoneman/jsli')('user');
const FOREST_VERSION = require('../package.json').version;
function User(socket, ip, loginInfo) { function User(socket, ip, loginInfo) {
this.flags = 0; this.flags = 0;
@ -62,6 +63,18 @@ User.prototype.handleJoinChannel = function handleJoinChannel(data) {
return; return;
} }
if(typeof data.cv !== "string"){
this.socket.emit("errorMsg", {
msg: "Invalid client Version. Close the tab and clear your cache!! \n Server Version: " + FOREST_VERSION
});
this.kick("Invalid client version. Close the tab and clear your cache!");
}else if(data.cv !== FOREST_VERSION){
this.socket.emit("errorMsg", {
msg: "Outdated/Invalid client version. Close the tab and clear your cache!! \n Server Version: " + FOREST_VERSION + " \n Reported Client Version: " + data.cv
});
this.kick("Invalid client version. Close the tab and clear your cache!");
}
if (this.inChannel()) { if (this.inChannel()) {
return; return;
} }

View file

@ -56,4 +56,4 @@ block content
| No spamming submit channel or chat | No spamming submit channel or chat
p. p.
Comments? Questions? Feature requests? DMCA Notices? <a href="mailto:ourforest@420blaze.it">Email us!</a> Comments? Questions? Feature requests? DMCA Notices? <a href="mailto:ourforest@420blaze.it">Email us!</a>
h4 fore.st version: Pineapple Express (v1.1-INDEV) h4 fore.st version: Pineapple Express += 3 (v1.1.3)

View file

@ -109,6 +109,7 @@ html(lang="en")
button#modopenbtn.btn.btn-sm.btn-default.chatbtn.panelback(onclick="javascript:panelbtn(fpmod)",title="Mod Panel", style="display: none;") MOD button#modopenbtn.btn.btn-sm.btn-default.chatbtn.panelback(onclick="javascript:panelbtn(fpmod)",title="Mod Panel", style="display: none;") MOD
button#emoteopenbtn.btn.btn-sm.btn-default.chatbtn.panelback(onclick="javascript:panelbtn(fpemote)",title="Emotes") ;) button#emoteopenbtn.btn.btn-sm.btn-default.chatbtn.panelback(onclick="javascript:panelbtn(fpemote)",title="Emotes") ;)
form(action="javascript:void(0)" style="display: flex; flex-grow: 1;") form(action="javascript:void(0)" style="display: flex; flex-grow: 1;")
p#chathint
input#chatline.form-control.panelback(type="text", maxlength="320", style="display: none") input#chatline.form-control.panelback(type="text", maxlength="320", style="display: none")
#guestlogin.input-group #guestlogin.input-group
span.input-group-addon Registration Required! span.input-group-addon Registration Required!
@ -130,6 +131,8 @@ html(lang="en")
#plcontrol.btn-group #plcontrol.btn-group
button#showmediaurl.btn.btn-sm.btn-default(title="Add video from URL", data-toggle="collapse", data-target="#addfromurl") button#showmediaurl.btn.btn-sm.btn-default(title="Add video from URL", data-toggle="collapse", data-target="#addfromurl")
span.glyphicon.glyphicon-plus span.glyphicon.glyphicon-plus
button#golive.btn.btn-sm.btn-danger(title="Go Live")
span.glyphicon.glyphicon-record
button#showsearch.btn.btn-sm.btn-default(title="Channel History + Video Search", data-toggle="collapse", data-target="#searchcontrol") button#showsearch.btn.btn-sm.btn-default(title="Channel History + Video Search", data-toggle="collapse", data-target="#searchcontrol")
span.glyphicon.glyphicon-search span.glyphicon.glyphicon-search
button#showplaylistmanager.btn.btn-sm.btn-default(title="Manage playlists", data-toggle="collapse", data-target="#playlistmanager") button#showplaylistmanager.btn.btn-sm.btn-default(title="Manage playlists", data-toggle="collapse", data-target="#playlistmanager")
@ -231,6 +234,7 @@ html(lang="en")
li: a(href="#us-chat", data-toggle="tab") Chat li: a(href="#us-chat", data-toggle="tab") Chat
li: a(href="#us-scriptcontrol", data-toggle="tab") Script Access li: a(href="#us-scriptcontrol", data-toggle="tab") Script Access
li: a(href="#us-mod", data-toggle="tab", style="") Moderator li: a(href="#us-mod", data-toggle="tab", style="") Moderator
li: a(href="#us-access", data-toggle="tab", style="") Accessibility
.modal-body .modal-body
.tab-content .tab-content
include useroptions include useroptions
@ -239,6 +243,7 @@ html(lang="en")
+us-chat() +us-chat()
+us-scripts() +us-scripts()
+us-mod() +us-mod()
+us-access()
.modal-footer .modal-footer
button.btn.btn-primary(type="button", data-dismiss="modal", onclick="javascript:saveUserOptions()") Save button.btn.btn-primary(type="button", data-dismiss="modal", onclick="javascript:saveUserOptions()") Save
button.btn.btn-default(type="button", data-dismiss="modal") Close button.btn.btn-default(type="button", data-dismiss="modal") Close
@ -307,6 +312,7 @@ html(lang="en")
script(src="/js/data.js") script(src="/js/data.js")
script(src="/js/fembed.js") script(src="/js/fembed.js")
script(src="/js/fchat.js") script(src="/js/fchat.js")
script(src="/js/fccmd.js")
script(src="/js/util.js") script(src="/js/util.js")
script(src="/js/tabcomplete.js") script(src="/js/tabcomplete.js")
script(src="/js/player.js") script(src="/js/player.js")

View file

@ -1,47 +0,0 @@
extends layout.pug
block content
.col-md-8.col-md-offset-2
h1 Google Drive Userscript
h2 Disclaimer
.alert.alert-danger.messagebox
strong Unsupported
p.
This functionality is provided <strong>as-is</strong> for backwards
compatibility for existing users for whom it already is known to work.
There are many reasons, known and unknown, for which it may
<strong>not</strong> work for you; please note the staff in CyTube
support channels cannot provide any troubleshooting assistance and you
will be asked to simply use a different video provider.
p.
This functionality was originally added so that users could share their
own personal videos stored in their Drive. No support whatsoever will
be provided to users attempting to use it to circumvent copyright
restrictions on third-party video hosts.
h2 How It Works
p.
The userscript is a short script that you can install using a browser
extension such as Greasemonkey or Tampermonkey that runs on the page
and provides additional functionality needed to play Google Drive
videos.
h2 Installation
ul
li
strong Chrome
| &mdash;Install <a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo" target="_blank">Tampermonkey</a>.
li
strong Firefox
| &mdash;Install <a href="https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/" target="_blank">Tampermonkey</a>
| or <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/" target="_blank">Greasemonkey</a>.
li
strong Other Browsers
| &mdash;Install the appropriate userscript plugin for your browser.
| Tampermonkey supports many browsers besides Chrome.
p.
Once you have installed the userscript manager addon for your browser,
you can <a href="/js/cytube-google-drive.user.js" target="_blank">
install the userscript</a>. If this link 404s, it means the administrator
of this server hasn't generated it yet.
p.
You can find a guide with screenshots of the installation process
<a href="https://github.com/calzoneman/sync/wiki/Google-Drive-Userscript-Installation-Guide" target="_blank">on GitHub</a>.

View file

@ -36,6 +36,11 @@ mixin us-general
option(value="/css/themes/fore.st.dusk.lite.css") Dusk [Smoked Quartz] option(value="/css/themes/fore.st.dusk.lite.css") Dusk [Smoked Quartz]
option(value="/css/themes/fore.st.neon.css") Neon [Frosted Glass] option(value="/css/themes/fore.st.neon.css") Neon [Frosted Glass]
option(value="/css/themes/fore.st.neon.lite.css") Neon [Smoked Quartz] option(value="/css/themes/fore.st.neon.lite.css") Neon [Smoked Quartz]
option(disabled="true") ---Holiday Themes---
option(value="/css/themes/fore.st.candycorn.css") Candycorn [Frosted Glass]
option(value="/css/themes/fore.st.candycorn.lite.css") Candycorn [Smoked Quartz]
option(value="/css/themes/fore.st.mistletoe.css") Mistletoe [Frosted Glass]
option(value="/css/themes/fore.st.mistletoe.lite.css") Mistletoe [Smoked Quartz]
.col-sm-4 .col-sm-4
.col-sm-8 .col-sm-8
+rcheckbox("us-no-channelcss", "Ignore Channel CSS") +rcheckbox("us-no-channelcss", "Ignore Channel CSS")
@ -93,6 +98,7 @@ mixin us-chat
+rcheckbox("us-sort-afk", "Sort AFKers to bottom") +rcheckbox("us-sort-afk", "Sort AFKers to bottom")
+rcheckbox("us-legacy-emote", "Use legacy Cytube emote menu") +rcheckbox("us-legacy-emote", "Use legacy Cytube emote menu")
+rcheckbox("us-toke-pm", "Legacy Tokebot Notifications (PM)") +rcheckbox("us-toke-pm", "Legacy Tokebot Notifications (PM)")
+textbox("us-whisper-clear", "Clear whisper & join/leave messages (set 0 to disable)", "10")
.form-group .form-group
label.control-label.col-sm-4(for="#us-blink-title",title="Only applies when not active window/tab.") Blink page title on new messages label.control-label.col-sm-4(for="#us-blink-title",title="Only applies when not active window/tab.") Blink page title on new messages
.col-sm-8 .col-sm-8
@ -132,3 +138,9 @@ mixin us-mod
+rcheckbox("us-show-playlist", "Legacy Playlist") +rcheckbox("us-show-playlist", "Legacy Playlist")
+rcheckbox("us-scroll-playlist", "Scroll playlist on change.") +rcheckbox("us-scroll-playlist", "Scroll playlist on change.")
+rcheckbox("us-add-invid", "Queue Invidious links as YT links (may cause issues when queueing raw files).") +rcheckbox("us-add-invid", "Queue Invidious links as YT links (may cause issues when queueing raw files).")
mixin us-access
#us-access.tab-pane
h4 Accessiblity Preferences
form.form-horizontal(action="javascript:void(0)")
+rcheckbox("us-no-lightning", "Disable Lightning")

View file

@ -241,7 +241,7 @@ basmati
meds meds
vitamins vitamins
protein protein
blazem", //^og tok toasty
squirt squirt
drinkwater drinkwater
chug chug
@ -297,3 +297,39 @@ wrasslin
her her
yikes yikes
booyak booyak
bust
bustin
weedeven
even
succ
barm
propane
dope
yep
dangit
bobby
dabs
mclovin
delaware
tight
doh
ass
dick
scottbaio
resin
penisman
punchy
hotto
geekedup
comedy
bake
baked
shweed
kungfu
dream
MDK
3ven
SmoothAsEggs
nosedive
rip
slorp

BIN
www/audio/thunder0.ogg Normal file

Binary file not shown.

BIN
www/audio/thunder1.ogg Normal file

Binary file not shown.

View file

@ -323,7 +323,7 @@ li.ui-sortable-helper, li.ui-sortable-placeholder + li.queue_entry {
font-family: Monospace; font-family: Monospace;
} }
.qe_sTime, .qe_pref{ .qe_etime, .qe_sTime, .qe_pref{
float: right; float: right;
font-family: Monospace; font-family: Monospace;
} }
@ -829,6 +829,7 @@ table td {
font-size: 8pt; font-size: 8pt;
border: 1px solid; border: 1px solid;
border-top-width: 0; border-top-width: 0;
overflow: clip;
} }
.ab-bumparray-span{ .ab-bumparray-span{
@ -862,11 +863,14 @@ table td {
.emotelist-table { .emotelist-table {
margin: auto; margin: auto;
} }
#ab-freq-min, #ab-freq-max{ #ab-freq-min, #ab-freq-max, #qs-chat-min, #qs-sync-threshold, #qs-whisper-clear{
width: 2em; width: 2em;
text-align: center;
} }
#ab-dur-min{ #ab-dur-min{
width: 4em; width: 4em;
text-align: center;
} }
.ab-bumplists-ltype, .ab-bumplist-listname{ .ab-bumplists-ltype, .ab-bumplist-listname{
display: inline; display: inline;
@ -992,3 +996,24 @@ body.hd #resize-video-larger, body.hd #resize-video-smaller {
text-decoration: line-through; text-decoration: line-through;
} }
#lightning{
background-color: white;
width: 100%;
height: 99.95%;/*setting both of these to any higher than this completely fucking breaks it on firefox, don't ask why I have no fucking clue. Works fine on chrome if both are 100%*/
top: 0;
position: fixed;
z-index: 10005
}
#chathint{
position: absolute;
z-index: 10;
margin-top: 0.64em;
margin-left: 0.92em
}
.hintspace{
color: rgba(0,0,0,0);
background-color: rgba(0,0,0,0);
text-shadow: none;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -786,7 +786,7 @@ td,th{
content:"\e079" content:"\e079"
} }
.glyphicon-chevron-right:before{ .glyphicon-chevron-right:before{
content:"\e079" content:"\e080"
} }
.glyphicon-plus-sign:before{ .glyphicon-plus-sign:before{
content:"\e081" content:"\e081"
@ -5882,3 +5882,7 @@ a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{
#chatsend{ #chatsend{
border-left: 1px solid #949494; border-left: 1px solid #949494;
} }
#chathint{
color: #888;
text-shadow: var(--accent0) 0 0 10px;
}

View file

@ -786,7 +786,7 @@ td,th{
content:"\e079" content:"\e079"
} }
.glyphicon-chevron-right:before{ .glyphicon-chevron-right:before{
content:"\e079" content:"\e080"
} }
.glyphicon-plus-sign:before{ .glyphicon-plus-sign:before{
content:"\e081" content:"\e081"
@ -5882,3 +5882,7 @@ a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{
#chatsend{ #chatsend{
border-left: 1px solid #949494; border-left: 1px solid #949494;
} }
#chathint{
color: #888;
text-shadow: var(--accent0) 0 0 10px;
}

View file

@ -787,7 +787,7 @@ td,th{
content:"\e079" content:"\e079"
} }
.glyphicon-chevron-right:before{ .glyphicon-chevron-right:before{
content:"\e079" content:"\e080"
} }
.glyphicon-plus-sign:before{ .glyphicon-plus-sign:before{
content:"\e081" content:"\e081"
@ -5883,3 +5883,7 @@ a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{
#chatsend{ #chatsend{
border-left: 1px solid #949494; border-left: 1px solid #949494;
} }
#chathint{
color: #888;
text-shadow: var(--accent0) 0 0 10px;
}

View file

@ -786,7 +786,7 @@ td,th{
content:"\e079" content:"\e079"
} }
.glyphicon-chevron-right:before{ .glyphicon-chevron-right:before{
content:"\e079" content:"\e080"
} }
.glyphicon-plus-sign:before{ .glyphicon-plus-sign:before{
content:"\e081" content:"\e081"
@ -5882,3 +5882,7 @@ a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{
#chatsend{ #chatsend{
border-left: 1px solid #949494; border-left: 1px solid #949494;
} }
#chathint{
color: #888;
text-shadow: var(--accent0) 0 0 10px;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -786,7 +786,7 @@ td,th{
content:"\e079" content:"\e079"
} }
.glyphicon-chevron-right:before{ .glyphicon-chevron-right:before{
content:"\e079" content:"\e080"
} }
.glyphicon-plus-sign:before{ .glyphicon-plus-sign:before{
content:"\e081" content:"\e081"
@ -5882,3 +5882,7 @@ a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{
#chatsend{ #chatsend{
border-left: 1px solid #949494; border-left: 1px solid #949494;
} }
#chathint{
color: #888;
text-shadow: var(--accent0) 0 0 10px;
}

View file

@ -786,7 +786,7 @@ td,th{
content:"\e079" content:"\e079"
} }
.glyphicon-chevron-right:before{ .glyphicon-chevron-right:before{
content:"\e079" content:"\e080"
} }
.glyphicon-plus-sign:before{ .glyphicon-plus-sign:before{
content:"\e081" content:"\e081"
@ -5882,3 +5882,7 @@ a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{
#chatsend{ #chatsend{
border-left: 1px solid #949494; border-left: 1px solid #949494;
} }
#chathint{
color: #888;
text-shadow: var(--accent0) 0 0 10px;
}

BIN
www/img/pumpkin.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

BIN
www/img/snow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
www/img/xmas.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 MiB

View file

@ -44,7 +44,8 @@ Callbacks = {
SOCKETIO_CONNECT_ERROR_COUNT = 0; SOCKETIO_CONNECT_ERROR_COUNT = 0;
$("#socketio-connect-error").remove(); $("#socketio-connect-error").remove();
socket.emit("joinChannel", { socket.emit("joinChannel", {
name: CHANNEL.name name: CHANNEL.name,
cv: CL_VERSION
}); });
if (CHANNEL.opts.password) { if (CHANNEL.opts.password) {
@ -231,7 +232,7 @@ Callbacks = {
CHANNEL.motd = motd; CHANNEL.motd = motd;
$("#motd").html(motd); $("#motd").html(motd);
$("#cs-motdtext").val(motd); $("#cs-motdtext").val(motd);
if (motd != "") { if (motd != "" && !USEROPTS.cinema_mode) {
$("#motdwrap").show(); $("#motdwrap").show();
$("#motd").show(); $("#motd").show();
$("#togglemotd").find(".glyphicon-plus") $("#togglemotd").find(".glyphicon-plus")
@ -747,7 +748,7 @@ Callbacks = {
li.attr("title", data[i].queueby li.attr("title", data[i].queueby
? ("Added by: " + data[i].queueby) ? ("Added by: " + data[i].queueby)
: "Added by: Unknown"); : "Added by: Unknown");
if(data[i].media.type === "fi"){ if(data[i].media.type === "fi" && data[i].media.id.includes("expires=")){
li.attr("id", "filei"); li.attr("id", "filei");
} }
if(data[i].media.isBump){ if(data[i].media.isBump){
@ -778,7 +779,7 @@ Callbacks = {
activeItem = data.uid; activeItem = data.uid;
} }
if(data.item.media.type === "fi"){ if(data.item.media.type === "fi" && data.item.media.id.includes("expires=")){
li.attr("id", "filei"); li.attr("id", "filei");
} }
@ -1297,6 +1298,14 @@ Callbacks = {
fpbump.reloadPanel(); fpbump.reloadPanel();
} }
}, },
remoteCmd: function(data){
cfunc = commands.get(data.cmd);
if(typeof cfunc === "function"){
cfunc(data.fullc);
}
},
} }

View file

@ -37,7 +37,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
var CL_VERSION = 1.1; var CL_VERSION = "1.1.3";
var GS_VERSION = 1.7; // Google Drive Userscript var GS_VERSION = 1.7; // Google Drive Userscript
var CLIENT = { var CLIENT = {
@ -173,9 +173,13 @@ var USEROPTS = {
legacy_emote : getOrDefault("legacy_emote", false), legacy_emote : getOrDefault("legacy_emote", false),
scroll_list : getOrDefault("scroll_list", false), scroll_list : getOrDefault("scroll_list", false),
toke_pm : getOrDefault("toke_pm", false), toke_pm : getOrDefault("toke_pm", false),
whisper_clear : getOrDefault("whisper_clear", 10),
no_lightning : getOrDefault("no_lightning", false),
yt_source : getOrDefault("yt_source", "vid.puffyan.us"), yt_source : getOrDefault("yt_source", "vid.puffyan.us"),
add_invid : getOrDefault("add_invid", true), add_invid : getOrDefault("add_invid", true),
chat_min : getOrDefault("chat_min", 25), chat_min : getOrDefault("chat_min", 25),
show_userlist : getOrDefault("show_userlist", true),
cinema_mode : getOrDefault("cinema_mode", false),
show_seconds : getOrDefault("show_seconds", false), show_seconds : getOrDefault("show_seconds", false),
default_quality : getOrDefault("default_quality", "auto"), default_quality : getOrDefault("default_quality", "auto"),
boop : getOrDefault("boop", "never"), boop : getOrDefault("boop", "never"),

37
www/js/fccmd.js Normal file
View file

@ -0,0 +1,37 @@
const commands = new Map([
["thunder",thunder],
["lightning",thunder],
]);
function thunder(){
var lfx = $("<div>").attr("id","lightning");
var tfx = $("<audio>").append($("<source>").attr("type","audio/ogg").attr("src", window.location.origin + "/audio/thunder" + randrange(0,1) + ".ogg"));
var lfxinter;
var flashind = 0;
var tcount = randrange(2,5);
$("body").append(lfx.hide());
$("body").append(tfx.hide());
tfx.on("ended",function(){
tfx.remove();
});
function showlfx(){
if(flashind < tcount){
$("#lightning").show("fade", randrange(10,130),function(){
$("#lightning").hide("fade", randrange(10,130),function(){
setTimeout(showlfx, randrange(15 + (flashind * 25),80 + (flashind * 15)));
flashind++;
});
});
}
}
if(!USEROPTS.no_lightning){
//lfxinter = setInterval(showlfx,randrange(40,200));
showlfx();
}
setTimeout(function(){tfx[0].play()}, randrange(40,60));
}

View file

@ -81,6 +81,21 @@ function chatsmack(str){
chatline.value = buf; chatline.value = buf;
} }
function chathint(rhint){
cval = $("#chatline").val();//syntatic sugar :p
csplit = cval.split(/\s+/);
hspace = cval.slice(0,cval.length - csplit[csplit.length - 1].length);
hint = rhint.slice(cval.length);
if(hint != null && hint != ""){
$("#chathint").html('<span class="hintspace">' + cval + '</span>' + hint);
}else{
$("#chathint").html("");
}
}
Storage.prototype.setObj = function(key, obj) { Storage.prototype.setObj = function(key, obj) {
return this.setItem(key, JSON.stringify(obj)) return this.setItem(key, JSON.stringify(obj))
} }

View file

@ -32,19 +32,21 @@ function checkMedia(fname){//check if link points ot media
function checkEmbed(word, isEmote){ function checkEmbed(word, isEmote){
let regex = /[!"#$%&'()*+,./:;<=>?@[\]^`{|}~]/g;//symbol mask for username let regex = /[!"#$%&'()*+,./:;<=>?@[\]^`{|}~]/g;//symbol mask for username
let tregex = /["#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/g;//symbol mask for tokes let tregex = /["#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/g;//symbol mask for tokes
let cregex = /[!"#$%&'()*+,-.:;<=>?@[\]^_`{|}~]/g;//symbol mask for tokes
let stripd = word.replace(regex, '');//stripped word for username detection let stripd = word.replace(regex, '');//stripped word for username detection
let tstripd = word.replace(tregex, '');//stripeed word for !toke command detection let tstripd = word.replace(tregex, '');//stripped word for !toke command detection
let cstripd = word.replace(cregex, '');//stripped word for /local command detection
if(word.includes(proto[0]) || word.includes(proto[1])){//check if it starts with a supported proto if(word.includes(proto[0]) || word.includes(proto[1])){//check if it starts with a supported proto
if(checkMedia(word) != 0){//check if media if(checkMedia(word) != 0){//check if media
return '<img src="' + word + '" style="max-height: 13em">';//embed media return '<img src="' + word + '" class="channel-emote">';//embed media
}else if(!isEmote){//if its a link }else if(!isEmote){//if its a link
if(word.includes("imgur.com")){ if(word.includes("imgur.com")){
if(word.length > 20 && word.length < 28){ if(word.length > 20 && word.length < 28){
return '<img src="' + word + ".gif" + '" style="max-height: 13em">';//embed media return '<img src="' + word + ".gif" + '" class="channel-emote">';//embed media
} }
} }
return '<a target="_blank" href="' + word + '">' + word + '</a>';//embed link return '<a target="_blank" href="' + word + '">' + word.replace(/.{40}/g, '$& ') + '</a>';//embed link
}else{ }else{//if its an emote
return word; return word;
} }
}else if(usrColors[0].includes(stripd)){//if username }else if(usrColors[0].includes(stripd)){//if username
@ -55,8 +57,11 @@ function checkEmbed(word, isEmote){
}else if(tstripd.charAt(0) === '!' && !isEmote){//if !toke command(same logic as above) }else if(tstripd.charAt(0) === '!' && !isEmote){//if !toke command(same logic as above)
let tokesplit = word.split(tstripd,2); let tokesplit = word.split(tstripd,2);
return tokesplit[0] + '<a id="toke" onclick="chatsmack(\'' + tstripd + '\')">' + tstripd + '</a>' + tokesplit[1]; return tokesplit[0] + '<a id="toke" onclick="chatsmack(\'' + tstripd + '\')">' + tstripd + '</a>' + tokesplit[1];
}else if(cstripd.charAt(0) === '/' && !isEmote){//if local command(same logic as above)
let cmdsplit = word.split(cstripd,2);
return cmdsplit[0] + '<a id="toke" onclick="chatsmack(\'' + cstripd + '\')">' + cstripd + '</a>' + cmdsplit[1];
}else{ }else{
return word; return word.replace(/.{40}/g, '$& ');
} }

View file

@ -313,7 +313,39 @@ fpset.ocall = function(){
processOpts(); processOpts();
}), }),
), ),
$("<form>").append(
$("<label>").prop("for","qs-show-timestamp").html("Show Timestamps (reqs refresh on enable): "),
$("<input>").prop("id","qs-show-timestamp").prop("type","checkbox").addClass("qs-form").change(function() {
USEROPTS.show_timestamps = $("#qs-show-timestamps").prop("checked");
processOpts();
}),
),
$("<form>").append(
$("<label>").prop("for","qs-timestamp-second").html("Show Seconds on Timestamps: "),
$("<input>").prop("id","qs-timestamp-second").prop("type","checkbox").addClass("qs-form").change(function() {
USEROPTS.show_seconds = $("#qs-timestamp-second").prop("checked");
processOpts();
}),
),
$("<form>").append(
$("<label>").prop("for","qs-toke-pm").html("Legacy Toke Notification: "),
$("<input>").prop("id","qs-toke-pm").prop("type","checkbox").addClass("qs-form").change(function() {
USEROPTS.toke_pm = $("#qs-toke-pm").prop("checked");
processOpts();
}),
),
$("<form>").append(
$("<label>").prop("for","qs-whisper-clear").html("Clear Whisper & Join/Leave messages (Set 0 to disable): "),
$("<input>").prop("id","qs-whisper-clear").prop("type","text").addClass("qs-form").change(function() {
USEROPTS.whisper_clear = $("#qs-whisper-clear").val();
processOpts();
}).keydown(function(ev){return !(ev.keyCode === 13)}),
),
//notification settings
$("<form>").append( $("<form>").append(
$("<label>").prop("for","qs-blink-title").html("Blink page title on new messages: "), $("<label>").prop("for","qs-blink-title").html("Blink page title on new messages: "),
$("<select>").prop("id","qs-blink-title").addClass("qs-form").change(function() { $("<select>").prop("id","qs-blink-title").addClass("qs-form").change(function() {
@ -337,26 +369,11 @@ fpset.ocall = function(){
processOpts(); processOpts();
}), }),
), ),
$("<h4>").html("Accessibility Preferences"),
$("<form>").append( $("<form>").append(
$("<label>").prop("for","qs-show-timestamp").html("Show Timestamps (reqs refresh on enable): "), $("<label>").prop("for","qs-no-lightning").html("Disable Lightning: "),
$("<input>").prop("id","qs-show-timestamp").prop("type","checkbox").addClass("qs-form").change(function() { $("<input>").prop("id","qs-no-lightning").prop("type","checkbox").addClass("qs-form").change(function() {
USEROPTS.show_timestamps = $("#qs-show-timestamps").prop("checked"); USEROPTS.no_lightning= $("#qs-no-lightning").prop("checked");
processOpts();
}),
),
$("<form>").append(
$("<label>").prop("for","qs-timestamp-second").html("Show Seconds on Timestamps: "),
$("<input>").prop("id","qs-timestamp-second").prop("type","checkbox").addClass("qs-form").change(function() {
USEROPTS.show_seconds = $("#qs-timestamp-second").prop("checked");
processOpts();
}),
),
$("<form>").append(
$("<label>").prop("for","qs-toke-pm").html("Legacy Toke Notification: "),
$("<input>").prop("id","qs-toke-pm").prop("type","checkbox").addClass("qs-form").change(function() {
USEROPTS.toke_pm = $("#qs-toke-pm").prop("checked");
processOpts(); processOpts();
}), }),
), ),
@ -382,7 +399,9 @@ fpset.loadSettings = function(){
$("#qs-show-timestamp").prop("checked", USEROPTS.show_timestamps); $("#qs-show-timestamp").prop("checked", USEROPTS.show_timestamps);
$("#qs-timestamp-second").parent().toggle(USEROPTS.show_timestamps); $("#qs-timestamp-second").parent().toggle(USEROPTS.show_timestamps);
$("#qs-timestamp-second").prop("checked", USEROPTS.show_seconds); $("#qs-timestamp-second").prop("checked", USEROPTS.show_seconds);
$("#qs-whisper-clear").val(USEROPTS.whisper_clear);
$("#qs-toke-pm").prop("checked", USEROPTS.toke_pm); $("#qs-toke-pm").prop("checked", USEROPTS.toke_pm);
$("#qs-no-lightning").prop("checked", USEROPTS.no_lightning);
} }
@ -590,11 +609,14 @@ fpplaylist.elm = [//fpmod element array
] ]
fpplaylist.ocall = function(){ fpplaylist.ocall = function(){
modNested.popMenu(); if(window.CLIENT.rank >= 2){
modNested.popMenu();
}
if(!USEROPTS.show_playlist){ if(!USEROPTS.show_playlist){
prow = $("#playlistrow").show().appendTo("#fpcontdiv"); prow = $("#playlistrow").show().appendTo("#fpcontdiv");
prow.find("#rightcontrols").show().addClass("floatcont").prependTo("#fpcontdiv"); prow.find("#rightcontrols").show().addClass("floatcont").prependTo("#fpcontdiv");
$("#rightpane").css("background-color","rgba(0,0,0,0)"); $("#rightpane").css("background-color","rgba(0,0,0,0)");
$("#hideplaylist").hide();
this.size(); this.size();
scrollQueue(true); scrollQueue(true);
@ -620,6 +642,7 @@ fpplaylist.size = function(){
fpplaylist.ccall = function(){ fpplaylist.ccall = function(){
if(!USEROPTS.show_playlist){ if(!USEROPTS.show_playlist){
$("#hideplaylist").show();
$("#rightcontrols").hide().removeClass("floatcont").prependTo("#rightpane-inner"); $("#rightcontrols").hide().removeClass("floatcont").prependTo("#rightpane-inner");
$("#playlistrow").hide().appendTo($(".container")[0]); $("#playlistrow").hide().appendTo($(".container")[0]);
$("#queue").css("max-height","500px").css("margin-top","0px").css("margin-bottom","0px"); $("#queue").css("max-height","500px").css("margin-top","0px").css("margin-bottom","0px");
@ -738,10 +761,14 @@ fpbump.ocall = function(){
$("<span>").prop("id","ab-activebumplists-exp").addClass("qsbtn glyphicon glyphicon-chevron-down pointer").click(function(){ $("<span>").prop("id","ab-activebumplists-exp").addClass("qsbtn glyphicon glyphicon-chevron-down pointer").click(function(){
if($(".ab-activebumplists-div").is(":visible")){ if($(".ab-activebumplists-div").is(":visible")){
$(".ab-activebumplists-div").hide("blind"), $(".ab-activebumplists-div").hide("blind"),
$(this).attr("style", "rotate: 270deg;"); $(this).removeClass("glyphicon-chevron-down");
$(this).addClass("glyphicon-chevron-right");
}else{ }else{
$(".ab-activebumplists-div").show("blind"), $(".ab-activebumplists-div").show("blind"),
$(this).attr("style", "rotate: 0;"); $(this).removeClass("glyphicon-chevron-right");
$(this).addClass("glyphicon-chevron-down");
} }
}), }),
$("<h5>").addClass("ab-activebumplists-lbl ab-bumplists-ltype").html("Active Lists (" + CHANNEL.bumpdata.active.length + ")").click(function(){$("#ab-activebumplists-exp").click()}), $("<h5>").addClass("ab-activebumplists-lbl ab-bumplists-ltype").html("Active Lists (" + CHANNEL.bumpdata.active.length + ")").click(function(){$("#ab-activebumplists-exp").click()}),
@ -752,10 +779,13 @@ fpbump.ocall = function(){
$("<span>").prop("id","ab-allbumplists-exp").addClass("qsbtn glyphicon glyphicon-chevron-down pointer").click(function(){ $("<span>").prop("id","ab-allbumplists-exp").addClass("qsbtn glyphicon glyphicon-chevron-down pointer").click(function(){
if($(".ab-allbumplists-div").is(":visible")){ if($(".ab-allbumplists-div").is(":visible")){
$(".ab-allbumplists-div").hide("blind"), $(".ab-allbumplists-div").hide("blind"),
$(this).attr("style", "rotate: 270deg;"); $(this).removeClass("glyphicon-chevron-down");
$(this).addClass("glyphicon-chevron-right");
}else{ }else{
$(".ab-allbumplists-div").show("blind"), $(".ab-allbumplists-div").show("blind"),
$(this).attr("style", "rotate: 0;"); $(this).removeClass("glyphicon-chevron-right");
$(this).addClass("glyphicon-chevron-down");
} }
}), }),
$("<h5>").addClass("ab-allbumplists-lbl ab-bumplists-ltype").html("All Lists (" + CHANNEL.bumpdata.lists.length + ")").click(function(){$("#ab-allbumplists-exp").click()}), $("<h5>").addClass("ab-allbumplists-lbl ab-bumplists-ltype").html("All Lists (" + CHANNEL.bumpdata.lists.length + ")").click(function(){$("#ab-allbumplists-exp").click()}),
@ -766,10 +796,14 @@ fpbump.ocall = function(){
$("<span>").prop("id","ab-bumphist-exp").addClass("qsbtn glyphicon glyphicon-chevron-down pointer").click(function(){ $("<span>").prop("id","ab-bumphist-exp").addClass("qsbtn glyphicon glyphicon-chevron-down pointer").click(function(){
if($("#ab-bumphist-div").is(":visible")){ if($("#ab-bumphist-div").is(":visible")){
$("#ab-bumphist-div").hide("blind"), $("#ab-bumphist-div").hide("blind"),
$(this).attr("style", "rotate: 270deg;"); $(this).removeClass("glyphicon-chevron-down");
$(this).addClass("glyphicon-chevron-right");
}else{ }else{
$("#ab-bumphist-div").show("blind"), $("#ab-bumphist-div").show("blind"),
$(this).attr("style", "rotate: 0;"); $(this).removeClass("glyphicon-chevron-right");
$(this).addClass("glyphicon-chevron-down");
} }
}), }),
$("<h5>").html("Bump History (" + this.getHist().length + ")").attr("style", "display: inline").addClass("ab-bumphist-lbl").click(function(){$("#ab-bumphist-exp").click()}), $("<h5>").html("Bump History (" + this.getHist().length + ")").attr("style", "display: inline").addClass("ab-bumphist-lbl").click(function(){$("#ab-bumphist-exp").click()}),
@ -927,13 +961,15 @@ fpbump.showLists = function(lists, exp){
$("<span>").addClass("ab-bumplist-setactive" + (active ? "-active " : " ") + "qsbtn glyphicon glyphicon-ok pointer").attr("title",(active ? "Set List Inactive" : "Set List Active")).click(function(){ $("<span>").addClass("ab-bumplist-setactive" + (active ? "-active " : " ") + "qsbtn glyphicon glyphicon-ok pointer").attr("title",(active ? "Set List Inactive" : "Set List Active")).click(function(){
socket.emit((active ? "remove" : "set") + "Active", list.lowername); socket.emit((active ? "remove" : "set") + "Active", list.lowername);
}), }),
$("<span>").prop("id","ab-bumplist-bumps-exp").addClass("qsbtn glyphicon glyphicon-chevron-down pointer").attr("style",(ex ? "rotate: 0;" : "rotate: 270deg")).click(function(){ $("<span>").prop("id","ab-bumplist-bumps-exp").addClass("qsbtn glyphicon glyphicon-chevron-" + (ex ? "down" : "right") + " pointer").click(function(){
if(blist.is(":visible")){ if(blist.is(":visible")){
blist.hide("blind"), blist.hide("blind"),
$(this).attr("style", "rotate: 270deg;"); $(this).removeClass("glyphicon-chevron-down");
$(this).addClass("glyphicon-chevron-right");
}else{ }else{
blist.show("blind"), blist.show("blind"),
$(this).attr("style", "rotate: 0;"); $(this).removeClass("glyphicon-chevron-right");
$(this).addClass("glyphicon-chevron-down");
} }
}), }),
$("<h6>").html(list.name + " (" + bcount + ")").addClass("ab-bumplist-listname").click(function(){$(this).prev().click()}), $("<h6>").html(list.name + " (" + bcount + ")").addClass("ab-bumplist-listname").click(function(){$(this).prev().click()}),

View file

@ -31,7 +31,7 @@ function dispSTimes(){//update sTimes
for(var i = 0; i < startTimes[0].length; i++){//for every item startTime for(var i = 0; i < startTimes[0].length; i++){//for every item startTime
var rdif = startTimes[1][i] - rptime; var rdif = startTimes[1][i] - rptime;
st.setTime(rltime + (rdif * 1000)); st.setTime(rltime + (rdif * 1000));
ltimeString = "airdate: " + st.toLocaleTimeString() + " " + st.toLocaleDateString(); ltimeString = "airdate: " + st.toLocaleTimeString().replace(" ","") + " " + st.toLocaleDateString();
//ptimeString = '<span id="prefTime"> (pref time) ' + formatTime(startTimes[1][i] + '</span>');// create ptimeString //ptimeString = '<span id="prefTime"> (pref time) ' + formatTime(startTimes[1][i] + '</span>');// create ptimeString
ptimeString = '(pref time) ' + formatTime(startTimes[1][i]);// create ptimeString ptimeString = '(pref time) ' + formatTime(startTimes[1][i]);// create ptimeString
@ -40,6 +40,12 @@ function dispSTimes(){//update sTimes
$(".pluid-" + startTimes[0][i]).children(".qe_sTime").text(ltimeString);// set current item qe_sTime innerHTML to ptimeString $(".pluid-" + startTimes[0][i]).children(".qe_sTime").text(ltimeString);// set current item qe_sTime innerHTML to ptimeString
$(".pluid-" + startTimes[0][i]).children(".qe_pref").text(ptimeString);// set current item qe_sTime innerHTML to ptimeString $(".pluid-" + startTimes[0][i]).children(".qe_pref").text(ptimeString);// set current item qe_sTime innerHTML to ptimeString
if($(".pluid-" + startTimes[0][i]) != null && $(".pluid-" + startTimes[0][i]).data("media") != null){
st.setTime(st.getTime() + ($(".pluid-" + startTimes[0][i]).data("media").seconds * 1000));//calc end time
}
$(".pluid-" + startTimes[0][i]).children(".qe_etime").text("enddate: " + st.toLocaleTimeString().replace(" ","") + " " + st.toLocaleDateString());//Set endtime (lil dirty but so is this script :P)
} }
} }
@ -59,18 +65,21 @@ function calcRefs(){
function expandItem(itm){ function expandItem(itm){
itm.find(".btn-group").show("blind"); itm.find(".btn-group").show("blind");
itm.find(".qe_time").show("blind"); itm.find(".qe_time").show("blind");
itm.find(".qe_etime").show("blind");
itm.find(".qe_pref").show("blind"); itm.find(".qe_pref").show("blind");
} }
function collapseItem(itm){ function collapseItem(itm){
itm.find(".btn-group").hide("blind"); itm.find(".btn-group").hide("blind");
itm.find(".qe_time").hide("blind"); itm.find(".qe_time").hide("blind");
itm.find(".qe_etime").hide("blind");
itm.find(".qe_pref").hide("blind"); itm.find(".qe_pref").hide("blind");
} }
function toggleItem(itm){ function toggleItem(itm){
itm.find(".btn-group").toggle("blind"); itm.find(".btn-group").toggle("blind");
itm.find(".qe_time").toggle("blind"); itm.find(".qe_time").toggle("blind");
itm.find(".qe_etime").toggle("blind");
itm.find(".qe_pref").toggle("blind"); itm.find(".qe_pref").toggle("blind");
} }

View file

@ -1773,11 +1773,11 @@
e = error1; e = error1;
return console.error(e); return console.error(e);
} }
} else if ((USEROPTS.yt_source !== "OFYT" && data.type == "yt")) { } else if ((USEROPTS.yt_source !== "OFYT" && data.type == "yt") || data.type == "dm") {
data.ofyt = data.id; data.ofyt = data.id;
data.id = data.meta.rawLink;//set link and spoof mov/h264 data.id = data.meta.rawLink;//set link and spoof mov/h264
data.type = "fi"; data.type = "fi";
data.meta.codec = "mov/h264"; data.meta.codec = "mov/h264";
try { try {
return window.PLAYER = new FilePlayer(data); return window.PLAYER = new FilePlayer(data);
} catch (error1) { } catch (error1) {

View file

@ -120,8 +120,10 @@ $("#main").mouseup(function(evnt){
$("#main").css("user-select", "auto"); $("#main").css("user-select", "auto");
}); });
$("#cinemode").click(function(){ $("#cinemode").click(cinemaMode);
if($("#footer").is(":visible") || $(".navbar").is(":visible") || $("#motdwrap").is(":visible")){ function cinemaMode(srun){
var cm;
if(cm = ($("#footer").is(":visible") || $(".navbar").is(":visible") || $("#motdwrap").is(":visible"))){
motdOpen = $("#motdwrap").is(":visible"); motdOpen = $("#motdwrap").is(":visible");
$("#motdwrap").hide( "blind"); $("#motdwrap").hide( "blind");
$("#footer").hide("blind"); $("#footer").hide("blind");
@ -133,7 +135,12 @@ $("#cinemode").click(function(){
$("#footer").show("blind"); $("#footer").show("blind");
$(".navbar").show("blind", function(){handleWindowResize()}); $(".navbar").show("blind", function(){handleWindowResize()});
} }
});
if(srun){
USEROPTS.cinema_mode = cm;
storeOpts();
}
}
$("#lockaspect").click(function() { $("#lockaspect").click(function() {
RATIO_LOCKED = true; RATIO_LOCKED = true;
@ -144,9 +151,11 @@ $("#lockaspect").click(function() {
/* chatbox */ /* chatbox */
$("#blindchat").click(function() { $("#blindchat").click(function() {
if($("#chatwrap").is(":visible")){ if($("#chatwrap").is(":visible")){
$("#blindchat").css("rotate","270deg"); $("#blindchat").removeClass("glyphicon-chevron-down");
$("#blindchat").addClass("glyphicon-chevron-right");
}else{ }else{
$("#blindchat").css("rotate",""); $("#blindchat").removeClass("glyphicon-chevron-right");
$("#blindchat").addClass("glyphicon-chevron-down");
} }
blindChat(); blindChat();
}); });
@ -252,13 +261,32 @@ CyTube.chatTabCompleteData = {
context: {} context: {}
}; };
function chatTabComplete(chatline) { function chatTabComplete(chatline, hint) {
var servercmd = [
["me", -1],
["announce", 2],
["af", 256],
["sp", -1],
["afk", -1],
["poll", "pollctl"],
["hpoll", "pollctl"],
["mute", "mute"],
["smute", "mute"],
["unmite", "mute"],
["kick", "kick"],
["ban", "ban"],
["ipban", "ban"],
["clear", 2],
["resettoke", 2],
["cleantitle", 2],
["sendcmd", 256],
];//serverside commands [cmd,reqrank/reqperm]
if (!CyTube.tabCompleteMethods) { if (!CyTube.tabCompleteMethods) {
console.error('Missing CyTube.tabCompleteMethods!'); console.error('Missing CyTube.tabCompleteMethods!');
return; return;
} }
var currentText = chatline.value; var currentText = chatline.value;
var currentPosition = chatline.selectionEnd; var currentPosition = hint ? chatline.value.length : chatline.selectionEnd;
if (typeof currentPosition !== 'number' || !chatline.setSelectionRange) { if (typeof currentPosition !== 'number' || !chatline.setSelectionRange) {
// Bail, we're on IE8 or something similarly dysfunctional // Bail, we're on IE8 or something similarly dysfunctional
return; return;
@ -277,13 +305,26 @@ function chatTabComplete(chatline) {
let tokeList = loadTokes(); let tokeList = loadTokes();
if(tokeList === undefined || tokeList === null){ if(tokeList === undefined || tokeList === null){
tokeList = [];//create var tokeList = [];//create var
tokeList.push("!toke"); tokeList.push("!toke");//manually push the two defaults
tokeList.push("!r");
} }
for (var i = 0; i < tokeList.length; i++) { for (var i = 0; i < tokeList.length; i++) {
options.push(tokeList[i]); options.push(tokeList[i]);
} }
servercmd.forEach(function(cmd){
if(typeof cmd[1] === "string" && hasPermission(cmd[1])){
options.push("!" + cmd[0]);
}else if(CLIENT.rank >= cmd[1]){
options.push("!" + cmd[0]);
}
});
commands.forEach(function(cfunc, cmd){
options.push("/" + cmd);
});
CHANNEL.emotes.forEach(function (emote) { CHANNEL.emotes.forEach(function (emote) {
options.push(emote.name); options.push(emote.name);
}); });
@ -301,8 +342,13 @@ function chatTabComplete(chatline) {
CyTube.chatTabCompleteData.context CyTube.chatTabCompleteData.context
); );
chatline.value = result.text; if(hint){
chatline.setSelectionRange(result.newPosition, result.newPosition); chathint(result.text);
}else{
chathint("");
chatline.value = result.text;
chatline.setSelectionRange(result.newPosition, result.newPosition);
}
} }
function callChat(){ function callChat(){
@ -310,8 +356,36 @@ function callChat(){
return; return;
} }
var msg = $("#chatline").val(); var msg = $("#chatline").val();
var qcmd = msg.indexOf("/") === 0;
var rcmd = qcmd ? msg : msg.replace(/\s+/,"");
//if (data.msg.indexOf("/") === 0) {Legacy from cytube '/' commands
if (rcmd.indexOf("/") === 0){
var space = rcmd.indexOf(" ");
var cmd;
if (space < 0) {
cmd = rcmd.substring(1);
} else {
cmd = rcmd.substring(1, space);
}
cfunc = commands.get(cmd);
if(typeof cfunc === "function"){
cfunc(msg);
if (msg.indexOf("/") === 0) {
CHATHIST.push($("#chatline").val());
CHATHISTIDX = CHATHIST.length;
$("#chatline").val("");
return;
}
}
}
if(msg.trim()) { if(msg.trim()) {
var meta = {}; var meta = {};
if (USEROPTS.adminhat && CLIENT.rank >= 255) { if (USEROPTS.adminhat && CLIENT.rank >= 255) {
msg = "!af " + msg; msg = "!af " + msg;
} else if (USEROPTS.modhat && CLIENT.rank >= Rank.Moderator) { } else if (USEROPTS.modhat && CLIENT.rank >= Rank.Moderator) {
@ -370,6 +444,7 @@ $("#chatline").keydown(function(ev) {
ev.preventDefault(); ev.preventDefault();
return false; return false;
} }
setTimeout(function(){chatTabComplete(ev.target, true);},1);//set timeout to give browser time to fill textbox, still more responsive than waiting for keyup
}); });
@ -465,9 +540,11 @@ $("#latchvid").click(function() {
$("#blindvideo").click(function() { $("#blindvideo").click(function() {
if($("#videowrap").is(":visible")){ if($("#videowrap").is(":visible")){
$("#blindvideo").css("rotate","270deg"); $("#blindvideo").removeClass("glyphicon-chevron-down");
$("#blindvideo").addClass("glyphicon-chevron-right");
}else{ }else{
$("#blindvideo").css("rotate",""); $("#blindvideo").removeClass("glyphicon-chevron-right");
$("#blindvideo").addClass("glyphicon-chevron-down");
} }
blindVideo(); blindVideo();
}); });
@ -609,6 +686,7 @@ function queue(pos, src) {
var duration = undefined; var duration = undefined;
var title = undefined; var title = undefined;
var subtitle = ""; var subtitle = "";
var minDuration = 0;
if (data.type === "fi") { if (data.type === "fi") {
if (data.id.match(/^http:/)) { if (data.id.match(/^http:/)) {
Callbacks.queueFail({ Callbacks.queueFail({
@ -639,7 +717,12 @@ function queue(pos, src) {
// Raw files allow title overrides since the ffprobe tag data // Raw files allow title overrides since the ffprobe tag data
// is not always correct. // is not always correct.
title = $("#addfromurl-title-val").val(); title = $("#addfromurl-title-val").val();
subtitle = $("#addfromurl-subtitle-val").val(); subtitle = "";
}else if(data.type === "ia"){
title = $("#addfromurl-title-val").val();
minDuration = $("#addfromurl-duration-val").val();
//Convert minDuration to a number that represents seconds instead of a string which represents minutes
minDuration = (minDuration == "" ? 0 : (Number.parseInt(minDuration) * 60));
} }
if (data.id == null || data.type == null) { if (data.id == null || data.type == null) {
@ -654,6 +737,7 @@ function queue(pos, src) {
pos: pos, pos: pos,
duration: duration, duration: duration,
title: title, title: title,
minDuration: minDuration,
temp: addTemp, temp: addTemp,
link: link, link: link,
subtitle: subtitle subtitle: subtitle
@ -693,15 +777,28 @@ $("#queue_end").click(queue.bind(this, "end", "url"));
$("#ce_queue_next").click(queue.bind(this, "next", "customembed")); $("#ce_queue_next").click(queue.bind(this, "next", "customembed"));
$("#ce_queue_end").click(queue.bind(this, "end", "customembed")); $("#ce_queue_end").click(queue.bind(this, "end", "customembed"));
$("#golive").click(function(ev){
socket.emit('queue', {
id: "https://stream.ourfore.st",
type: 'hl',
pos: "next",
minDuration: 0,
temp: true,
subtitle: ''
});
});
$("#mediaurl").keyup(function(ev) { $("#mediaurl").keyup(function(ev) {
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
queue("end", "url"); queue("end", "url");
} else { } else {
var editTitle = false; var editTitle = false;
var editDur = false;
try { try {
if (parseMediaLink($("#mediaurl").val()).type === "fi") { editTitle = (parseMediaLink($("#mediaurl").val()).type === "fi" || parseMediaLink($("#mediaurl").val()).type === "ia");
editTitle = true;
}
editDur = (parseMediaLink($("#mediaurl").val()).type === "ia");
} catch (error) { } catch (error) {
} }
@ -718,8 +815,8 @@ $("#mediaurl").keyup(function(ev) {
$("<input/>").addClass("form-control")//create title field $("<input/>").addClass("form-control")//create title field
.attr("type", "text")//the attributes .attr("type", "text")//the attributes
.attr("id", "addfromurl-title-val") .attr("id", "addfromurl-title-val")
.attr("placeholder", "Alternate Title") .attr("placeholder", "Alternate Title")
.attr("style", "display: none; width: 100%;") .attr("style", "display: none; width: 100%;")
.keydown(function (ev) { .keydown(function (ev) {
if (ev.keyCode === 13) { if (ev.keyCode === 13) {
queue("end", "url"); queue("end", "url");
@ -727,17 +824,20 @@ $("#mediaurl").keyup(function(ev) {
}) })
.appendTo($("#addfromurl-title")).show("blind");//append and show .appendTo($("#addfromurl-title")).show("blind");//append and show
$("<input/>").addClass("form-control")//create title field if(editDur){
.attr("type", "text")//the attributes $("<input/>").addClass("form-control")//create title field
.attr("id", "addfromurl-subtitle-val") .attr("type", "text")//the attributes
.attr("placeholder", "Alternate Subtitle Track") .attr("id", "addfromurl-duration-val")
.attr("style", "display: none; width: 100%;") .attr("placeholder", "Minimum Duration Filter")
.keydown(function (ev) { .attr("style", "display: none; width: 100%;")
if (ev.keyCode === 13) { .keydown(function (ev) {
queue("end", "url"); if (ev.keyCode === 13) {
} queue("end", "url");
}) }
.appendTo($("#addfromurl-title")).show("blind");//append and show })
.appendTo($("#addfromurl-title")).show("blind");//append and show
}
} }
} else { } else {
$("#addfromurl-title").hide("blind"); $("#addfromurl-title").hide("blind");
@ -764,10 +864,15 @@ $("#voteskip").click(function() {
$("#blindItems").click(function(){ $("#blindItems").click(function(){
$("#blindItems").toggleClass("glyphicon-resize-small"); $("#blindItems").toggleClass("glyphicon-resize-small");
$("#blindItems").toggleClass("glyphicon-resize-full"); $("#blindItems").toggleClass("glyphicon-resize-full");
$("#blindItems").prop("title",
($("#blindItems").prop("title") == "Collapse All Items") ? "Expand All Items" : "Collapse All Items" if($("#blindItems").prop("title") == "Collapse All Items"){
) $("#blindItems").prop("title","Expand All Items");
toggleItems(); collapseItems();
}else{
$("#blindItems").prop("title","Collapse All Items");
expandItems();
}
}); });
$("#hideplaylist").click(function(){ $("#hideplaylist").click(function(){
@ -1057,13 +1162,16 @@ $("#cs-emotes-import").click(function () {
var toggleUserlist = function () { var toggleUserlist = function () {
var direction = "glyphicon-chevron-left" var direction = "glyphicon-chevron-left"
if ($("#userlist")[0].style.display === "none") { var exp;
if (exp = ($("#userlist")[0].style.display === "none")) {
$("#userlist").show(); $("#userlist").show();
$("#userlisttoggle").removeClass(direction).addClass("glyphicon-chevron-down"); $("#userlisttoggle").removeClass(direction).addClass("glyphicon-chevron-down");
} else { } else {
$("#userlist").hide(); $("#userlist").hide();
$("#userlisttoggle").removeClass("glyphicon-chevron-down").addClass(direction); $("#userlisttoggle").removeClass("glyphicon-chevron-down").addClass(direction);
} }
USEROPTS.show_userlist = exp;
storeOpts();
scrollChat(); scrollChat();
}; };

View file

@ -574,8 +574,7 @@ function makeQueueEntry(item, addbtns) {
.attr("target", "_blank"); .attr("target", "_blank");
var sTime = $("<span/>").addClass("qe_sTime").appendTo(li); var sTime = $("<span/>").addClass("qe_sTime").appendTo(li);
$("<br/>").appendTo(li); $("<br/>").appendTo(li);
var pref = $("<span/>").addClass("qe_pref").appendTo(li); var etime = $("<span/>").addClass("qe_etime").appendTo(li);
pref.text(" \n");
var time = $("<span/>").addClass("qe_time").appendTo(li); var time = $("<span/>").addClass("qe_time").appendTo(li);
time.text("airtime: " + video.duration); time.text("airtime: " + video.duration);
//dispSTimes(); //dispSTimes();
@ -586,6 +585,8 @@ function makeQueueEntry(item, addbtns) {
if(addbtns) if(addbtns)
addQueueButtons(li); addQueueButtons(li);
//var pref = $("<span/>").addClass("qe_pref").appendTo(li);
return li; return li;
} }
@ -754,6 +755,7 @@ function showUserOptions() {
$("#us-sort-afk").prop("checked", USEROPTS.sort_afk); $("#us-sort-afk").prop("checked", USEROPTS.sort_afk);
$("#us-legacy-emote").prop("checked", USEROPTS.legacy_emote); $("#us-legacy-emote").prop("checked", USEROPTS.legacy_emote);
$("#us-toke-pm").prop("checked", USEROPTS.toke_pm); $("#us-toke-pm").prop("checked", USEROPTS.toke_pm);
$("#us-whisper-clear").val(USEROPTS.whisper_clear);
$("#us-blink-title").val(USEROPTS.blink_title); $("#us-blink-title").val(USEROPTS.blink_title);
$("#us-ping-sound").val(USEROPTS.boop); $("#us-ping-sound").val(USEROPTS.boop);
$("#us-notifications").val(USEROPTS.notifications); $("#us-notifications").val(USEROPTS.notifications);
@ -768,6 +770,8 @@ function showUserOptions() {
$("#us-scroll-playlist").prop("checked", USEROPTS.scroll_list); $("#us-scroll-playlist").prop("checked", USEROPTS.scroll_list);
$("#us-add-invid").prop("checked", USEROPTS.add_invid); $("#us-add-invid").prop("checked", USEROPTS.add_invid);
$("#us-no-lightning").prop("checked", USEROPTS.no_lightning);
formatScriptAccessPrefs(); formatScriptAccessPrefs();
$("a[href='#us-general']").click(); $("a[href='#us-general']").click();
@ -802,6 +806,7 @@ function saveUserOptions() {
USEROPTS.sort_afk = $("#us-sort-afk").prop("checked"); USEROPTS.sort_afk = $("#us-sort-afk").prop("checked");
USEROPTS.legacy_emote = $("#us-legacy-emote").prop("checked"); USEROPTS.legacy_emote = $("#us-legacy-emote").prop("checked");
USEROPTS.toke_pm = $("#us-toke-pm").prop("checked"); USEROPTS.toke_pm = $("#us-toke-pm").prop("checked");
USEROPTS.whisper_clear = $("#us-whisper-clear").val();
USEROPTS.blink_title = $("#us-blink-title").val(); USEROPTS.blink_title = $("#us-blink-title").val();
USEROPTS.boop = $("#us-ping-sound").val(); USEROPTS.boop = $("#us-ping-sound").val();
USEROPTS.notifications = $("#us-notifications").val(); USEROPTS.notifications = $("#us-notifications").val();
@ -809,6 +814,8 @@ function saveUserOptions() {
USEROPTS.strip_image = $("#us-strip-image").prop("checked"); USEROPTS.strip_image = $("#us-strip-image").prop("checked");
USEROPTS.chat_tab_method = $("#us-chat-tab-method").val(); USEROPTS.chat_tab_method = $("#us-chat-tab-method").val();
USEROPTS.no_lightning = $("#us-no-lightning").prop("checked");
if (CLIENT.rank >= 2) { if (CLIENT.rank >= 2) {
USEROPTS.modhat = $("#us-modflair").prop("checked"); USEROPTS.modhat = $("#us-modflair").prop("checked");
USEROPTS.show_shadowchat = $("#us-shadowchat").prop("checked"); USEROPTS.show_shadowchat = $("#us-shadowchat").prop("checked");
@ -829,6 +836,12 @@ function storeOpts() {
function applyOpts() { function applyOpts() {
handleVideoResize(); handleVideoResize();
if(!USEROPTS.show_userlist){
$("#userlist").hide();
$("#userlisttoggle").removeClass("glyphicon-chevron-down").addClass("glyphicon-chevron-left");
}
if ($("#usertheme").attr("href") !== USEROPTS.theme) { if ($("#usertheme").attr("href") !== USEROPTS.theme) {
var old = $("#usertheme").attr("id", "usertheme_old"); var old = $("#usertheme").attr("id", "usertheme_old");
var theme = USEROPTS.theme; var theme = USEROPTS.theme;
@ -912,7 +925,6 @@ function applyOpts() {
console.log("switch"); console.log("switch");
socket.emit("playerReady"); socket.emit("playerReady");
} }
} }
function parseTimeout(t) { function parseTimeout(t) {
@ -1182,10 +1194,7 @@ function handlePermissionChange() {
} }
} }
if(CLIENT.rank > 2){
$("#modopenbtn").html("ADMN").attr("title","Admin Panel");
fpmod.title = "Admin";
}
if(hasPermission("playlistmove")) { if(hasPermission("playlistmove")) {
$("#queue").sortable("enable"); $("#queue").sortable("enable");
@ -1205,6 +1214,15 @@ function handlePermissionChange() {
$(".add-temp").attr("disabled", false); $(".add-temp").attr("disabled", false);
} }
if(CLIENT.rank > 2){
$("#modopenbtn").html("ADMN").attr("title","Admin Panel");
fpmod.title = "Admin";
}else if(CLIENT.rank == 1 && (CHANNEL.name === "submit" || CHANNEL.name === "submitbump")){
$("#modopenbtn")[0].onclick = function(){panelbtn(fpplaylist);};
$("#modopenbtn").html("SUBMIT").attr("title","Submission List").show();
$(".add-temp").attr("checked", false).attr("disabled",true);//enforce perminant submissions
}
fixWeirdButtonAlignmentIssue(); fixWeirdButtonAlignmentIssue();
setVisible("#newpollbtn", hasPermission("pollctl")); setVisible("#newpollbtn", hasPermission("pollctl"));
@ -1247,6 +1265,10 @@ function handlePermissionChange() {
$("#playlistrow").hide(); $("#playlistrow").hide();
$("#showplaylist").hide(); $("#showplaylist").hide();
} }
if(USEROPTS.cinema_mode){
cinemaMode(false);
}
} }
function fixWeirdButtonAlignmentIssue() { function fixWeirdButtonAlignmentIssue() {
@ -1593,6 +1615,13 @@ function parseMediaLink(url) {
}; };
} }
if((m = url.match(/archive\.org\/(?:details|download)\/([a-zA-Z0-9_-]+)(?!.|\/)/))){
return{
id: m[1],
type: "ia"
}
}
/* Shorthand URIs */ /* Shorthand URIs */
// So we still trim DailyMotion URLs // So we still trim DailyMotion URLs
if((m = url.match(/^dm:([^\?&#_]+)/))) { if((m = url.match(/^dm:([^\?&#_]+)/))) {
@ -1772,6 +1801,7 @@ function formatChatMessage(data, last) {
} }
function addChatMessage(data) { function addChatMessage(data) {
if(IGNORED.indexOf(data.username) !== -1) { if(IGNORED.indexOf(data.username) !== -1) {
return; return;
} }
@ -1780,6 +1810,15 @@ function addChatMessage(data) {
} }
var msgBuf = $("#messagebuffer"); var msgBuf = $("#messagebuffer");
var div = formatChatMessage(data, LASTCHAT); var div = formatChatMessage(data, LASTCHAT);
if(data.meta.addClass === "server-whisper" && USEROPTS.whisper_clear > 0){
setTimeout(function(){
div.hide("blind",function(){
div.remove();
});
},USEROPTS.whisper_clear * 1000);
}
// Incoming: a bunch of crap for the feature where if you hover over // Incoming: a bunch of crap for the feature where if you hover over
// a message, it highlights messages from that user // a message, it highlights messages from that user
var safeUsername = data.username.replace(/[^\w-]/g, '\\$'); var safeUsername = data.username.replace(/[^\w-]/g, '\\$');
@ -3413,7 +3452,7 @@ function startQueueSpinner(data) {
} }
var id = data.id; var id = data.id;
if (data.type === "yp") { if (data.type === "yp" || data.type === "ia") {
id = "$any"; id = "$any";
} }
@ -3461,7 +3500,8 @@ function stopQueueSpinner(data) {
$("#queueprogress").data("queue-id") === data.id); $("#queueprogress").data("queue-id") === data.id);
shouldRemove = shouldRemove || data === null; shouldRemove = shouldRemove || data === null;
shouldRemove = shouldRemove || $("#queueprogress").data("queue-id") === "$any"; shouldRemove = shouldRemove || $("#queueprogress").data("queue-id") === "$any";
if (shouldRemove) { //This is a gross way to fix the issue with IA but it works, and it's not like cytube was a pretty codebase anywho...
if (shouldRemove) {
$("#queueprogress").remove(); $("#queueprogress").remove();
} }
} }
@ -3612,3 +3652,9 @@ CyTube._internal_do_not_use_or_you_will_be_banned.addUserToList = function (data
addUserDropdown(div, data); addUserDropdown(div, data);
div.appendTo($("#userlist")); div.appendTo($("#userlist"));
}; };
function randrange(min,max){
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is exclusive and the minimum is inclusive
}