-
Notifications
You must be signed in to change notification settings - Fork 80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Develop a hosting feature so streamers can recommend other channels to their community when they are offline. #786
Conversation
|> preload(host: [:user], target: [:user]) | ||
|> Repo.one() | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've used raw sql in the following methods for clarity purposes. The Ecto.Query format was getting hard to read.
|> Repo.all() | ||
|> length() | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've noted the following method in my "Performance Considerations" section. This query mostly utilizes joins on indexes but I have not been able to test it with a large dataset.
@@ -202,4 +258,38 @@ defmodule Glimesh.ChannelLookups do | |||
def get_any_channel_for_user(user) do | |||
Repo.one(from c in Channel, where: c.user_id == ^user.id) | |||
end | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following method is used by the add hosts text input box type-ahead code. Since it takes raw user input, I attempt to sanitize that input against '\', '_', and '%' characters.
@@ -44,6 +46,9 @@ defmodule Glimesh.Streams.Channel do | |||
field :poster, Glimesh.ChannelPoster.Type | |||
field :chat_bg, Glimesh.ChatBackground.Type | |||
|
|||
# This is used when searching for live channels that are live or hosted |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use this virtual field on the "Following" page to determine if the matching stream was the result of the user directly following the live stream or the result of the user following an offline channel that is hosting a live stream. Basically, this determines if I should put the "Hosted" gold badge on the stream's image on the page.
socket | ||
|> assign(:channel, channel) | ||
|> assign(:allow_changeset, Channel.change_allow_hosting(channel)) | ||
|> put_flash(:hosting_info, gettext("Saved Hosting Preference."))} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use "hosting_info" instead of just "info" for the flash messages due to a bug I've reported: 785
@@ -2,14 +2,14 @@ | |||
<div class="position-relative overflow-hidden p-3 p-md-5 m-md-3 text-center"> | |||
<div class="col-md-12 mx-auto "> | |||
<h1 class="display-4 font-weight-normal"> | |||
<%= gettext("Live Followed Streams") %> | |||
<%= gettext("Live Streams") %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The word "Followed" here seemed redundant so I removed it.
</h1> | ||
<%= if length(@channels) == 0 do %> | ||
<p><%= gettext("None of the streams you follow are live.") %></p> | ||
<% end %> | ||
</div> | ||
</div> | ||
<div class="row"> | ||
<div class="row d-flex justify-content-center"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added "d-flex" and "justify-content-center" so the stream cards will now align with the center of the page if there are less then three on a row. The previous left-justification felt a bit ugly to me.
:redirect_to_hosted_target => redirect_to_hosted_target, | ||
:hosting_channel => hosting_channel | ||
}} = get_hosting_data(params, channel, maybe_user, streamer) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrapped the existing mount code in an if statement to check if we should redirect immediately if hosting another channel.
@@ -1,8 +1,9 @@ | |||
<!-- app.html.eex --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've found it saves debugging effort later on to wrap shared html code snippets in a comment that describes where it comes from.
Hey @MemoryLeakDeath, amazing work on this! I've reviewed the PR description so far and everything seems like a very good approach. I'm going to start looking over the code and testing it out locally, and will let you know later this week what feedback I have on it! The PR has also come at a great time, as we're not working on any major rewrites that would render this PR stale, so I hope we can get it through the pipeline asap! |
Great! I could not figure out how to run Rhianna jobs locally and that's
the part that I'm worried the most about.
…On Tue, Jan 4, 2022 at 5:03 PM Luke Strickland ***@***.***> wrote:
Hey @MemoryLeakDeath <https://github.com/MemoryLeakDeath>, amazing work
on this! I've reviewed the PR description so far and everything seems like
a very good approach. I'm going to start looking over the code and testing
it out locally, and will let you know later this week what feedback I have
on it! The PR has also come at a great time, as we're not working on any
major rewrites that would render this PR stale, so I hope we can get it
through the pipeline asap!
—
Reply to this email directly, view it on GitHub
<#786 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABHHQYM55QJOQVSXU46VV63UUNVCHANCNFSM5LGUGDNQ>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Assuming you have a full dev instance running, you can run Rihanna jobs locally (and they'll keep registering themselves to run) by entering an iex shell with |
Ah excellent, thanks!
…On Tue, Jan 4, 2022 at 8:07 PM Luke Strickland ***@***.***> wrote:
Assuming you have a full dev instance running, you can run Rihanna jobs
locally (and they'll keep registering themselves to run) by entering an iex
shell with iex -S mix inside the project directory, and then running Rihanna.schedule(Glimesh.Jobs.AutoHostCron,
[], in: 1) to schedule the job in 1 second.
—
Reply to this email directly, view it on GitHub
<#786 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABHHQYPI3NJJTBE4WOSUSW3UUOKWRANCNFSM5LGUGDNQ>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Thanks for the work! It'll be on production soon. |
Awesome! Thanks!
…On Sat, Jan 15, 2022 at 8:03 AM Luke Strickland ***@***.***> wrote:
Thanks for the work! It'll be on production soon.
—
Reply to this email directly, view it on GitHub
<#786 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABHHQYOSM52RG5ZYGZPEWU3UWFWCDANCNFSM5LGUGDNQ>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Summary
Hosting is a process by which a streamer may select other streams to promote to their community when their stream is offline. Typically the streamer will manage a list of other streams to promote ahead of time and the hosting process will commence without their direct intervention. This is in stark contrast to Raiding which must be initiated while live and will actively pull the viewers from the initiating live stream to the target live stream.
Feature Scope (Minimum Viable Feature)
Features Outside Initial Scope
New Channel Settings Page: Hosting
From the new hosting settings page, streamers can add/remove channels to host and choose whether or not to allow other streamers to host their channel. Hosting other channels does NOT require a streamer allow others to host them.
Hosting Qualifications
To host other channels a user must meet these qualifications:
Furthermore, a streamer can only select channels for hosting that meet these qualifications:
NOTE: The auto hosting job will check these qualifications on each run and should handle scenarios where a host or target is temporarily disqualified (for instance, banned then un-banned) without intervention from the streamers.
Allow hosting toggle
"Other streamers may host my channel" is a simple true/false toggle that will save automatically upon change. It defaults to not allow (false) so all new and existing streamers will have to turn it on if they so desire. It affects a new column added to the "channels" database table ("allow_hosting", boolean, nullable: true, default: false)
Add channel field/button
Streamers can type/paste the name of another channel into the add channel field then click the button to attempt to add that channel to their hosting list. The add may fail if the channel in question does not meet the hosting qualifications listed in the previous section.
The input field supports case-insensitive, type-ahead functionality and will provide up to 10 suggestions for channels whose name starts with the text input, meet the hosting qualifications specified above, and are not already on the streamer's hosting list. The maximum length of the input field is 24 since that is the maximum username/displayname size enforced by the system when creating users. The input field is sanitized against the following characters that have special meaning in an "ilike" sql statement: ('\', '_', '%') Though security researchers do not recommend using "like" statements, attempting to sanitize the postgres text search mechanisms looked far more daunting. My hope is the 24 character max length, the sanitization regex, and the use of sql replacement tokens will be sufficient to curtail sql injection attempts against this field.
Development notes
The Typeahead field is separated into it's own live_component so it can potentially be re-used in future pages. It is composed of the following files: "channel_lookup_typeahead.ex", "channel-lookup-typeahead.scss", and "ChannelLookupTypeahead.js".
Hosting channel list
The hosting channel list displays what channels the streamer has selected for their hosting rotation, the status of the hosting target, the last time the target was hosted by the system, and a remove button to remove the target from the list.
Status codes
= "ready"; The target channel is in the hosting rotation.
= "hosting"; The target channel is currently being hosted. A maximum of one such target can be in this status.
= "error"; The target channel no longer qualifies for hosting and is not in rotation. This can be a temporary status as it is based on all the hosting qualification checks enumerated earlier in this document and will be re-checked every time the auto-hosting background job runs.
Last hosted and selection of next channel to host
The auto host job selects the next channel to host from the hosting list as follows:
Development notes
This list is stored in a new database table with the following structure:
Being Hosted, Channel Page
Viewers who arrive on an offline channel page that is hosting another channel will be automatically redirected to the hosted channel and presented with a message at the top of the screen informing them that the channel they originally went to is hosting another channel. Viewers can dismiss the message by clicking the "x" icon at the top right of the message box. Viewers can return to the hosting channel by clicking the "Return to host" button. The message has the following format:
"{host avatar} {host channel name} is hosting {target channel name} {target channel avatar}"
Hosting Another Channel, Channel Page
The owner of the hosting channel, viewers who have clicked "Return to Host", or viewers that have followed a link to a host's channel page that contains "?follow_host=false" at the end of the URL will be presented with a message that this channel is hosting another channel. Viewers may then click the "Go There" button to go to the stream being hosted or can dismiss the message by clicking the "x" at the top-right corner of the message box.
The channel owner will not automatically redirect to a channel they are hosting as this would be inconvenient if they are prepping to go live or change their stream title/tags/category (channel moderators WILL redirect in this current iteration). A channel owner that wishes to advertise a link to their channel (for instance in a pinned tweet or a linktr.ee page) can add "?follow_host=false" to the end of their channel URL so visitors will not automatically redirect to a channel being hosted. For example:
https://glimesh.tv/memtest86/?follow_host=false
Development Notes
The Following Page
Viewers who follow channels that are hosting other channels will now see those hosted channels in their following page with a special "Hosted" badge on them. This is in addition to seeing live channels they follow.
The database query that populates this page attempts to do the following:
The Following Badge (count)
Viewers will see a notification badge next to the "Following" category at the top of the page when one or more channels they are following are live. This "call to action" is presented as a white number on a red background.
In an effort to find a middle ground where we can promote hosted streams without making the following badge too noisy (thus prompting users to ignore it), I've added a white number on blue badge case. This number will ONLY appear when all the channels a viewer is following are offline but at least some of them are hosting other channels. Similar to the red badge, the blue badge number reflects the total number of hosted channels.
Auto Host Job
The linchpin of the hosting feature is the auto hosting job. This job utilizes Rihanna to run every 10 minutes, so streamers should not expect to see their channel begin to host others immediately upon going offline. That said, viewers will no longer be redirected to a host-ed channel when the host-ing channel goes live (even though it may take up to 10 minutes for the database to reflect that the channel is no longer host-ing another). This job has the following tasks to perform in order:
After each step above, the job will write an info level log with the number of records updated. I decided to keep these update statements as raw sql since they are fairly complex and were becoming hard to read in Ecto.Query form. The job does not attempt to retry if it fails.
Security Considerations
The following are points to consider when evaluating this feature from a security standpoint. I've made every effort to code in a secure manner but that does not mean I haven't goofed; especially given my inexperience with the Elixir language.
Performance Considerations
The following are points to consider when evaluating this feature from a performance standpoint. Whenever possible, I have offloaded much of the heavy lifting to the database. However, I only have a small development database with few records in it so I'm not able to really test this feature under load.
Additional Considerations
This is my first feature I've written in the Elixir language so a thorough code review would be much appreciated. Any feedback on code style/structure would also be welcome. I'm not completely unfamiliar with programming; I've worked professionally in Java for 18 years and 1 year in Ruby on Rails (and dabbled in other languages as hobby projects).