Skip to content

Commit

Permalink
restructure youtube receiver
Browse files Browse the repository at this point in the history
  • Loading branch information
decahedron1 committed Feb 9, 2024
1 parent 9fc2981 commit 8927b82
Show file tree
Hide file tree
Showing 18 changed files with 1,310 additions and 776 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ rust-version = "1.75"

[dependencies]
irc = { version = "0.15", optional = true, default-features = false }
tokio = { version = "1.0", default-features = false, features = [ "net" ] }
tokio = { version = "1", default-features = false, features = [ "net" ] }
futures-util = { version = "0.3", default-features = false }
thiserror = "1.0"
chrono = { version = "0.4", default-features = false, features = [ "clock", "std" ] }
Expand Down
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
# `brainrot`
A live chat interface for Twitch & YouTube written in Rust.

## Features
- <img src="https://www.twitch.tv/favicon.ico" width="14" /> **Twitch**
* ⚡ Live IRC
* 🔓 No authentication required
- <img src="https://www.youtube.com/favicon.ico" width="14" /> **YouTube**
* 🏆 Receive chats in real time - first library to do so
* ⚡ Low latency
* ⏪ Supports VODs
* 🔓 No authentication required

## Usage
See [`examples/main.rs`](https://github.com/vitri-ent/brainrot/blob/examples/main.rs).
See [`examples/twitch.rs`](https://github.com/vitri-ent/brainrot/blob/examples/twitch.rs) & [`examples/youtube.rs`](https://github.com/vitri-ent/brainrot/blob/examples/youtube.rs).

```shell
$ cargo run --example main -- sinder
$ cargo run --example twitch -- sinder
Spartan_N1ck: Very Generous
luisfelipee23: GIGACHAD
wifi882: GIGACHAD
Expand All @@ -15,4 +25,15 @@ buddy_boy_joe: @sharkboticus ah LOL fair enough sinder6Laugh sinder6Laugh sinder
KateRosaline14: Merry Christmas
ThrillGamer2002: FirstTimeChatter
...

$ cargo run --example youtube -- "@FUWAMOCOch"
Konami Code: makes sense
Wicho4568🐾: thank you biboo
retro: Lol
GLC H 🪐: Thanks Biboo? :face-blue-smiling::FUWAhm:
Ar5eN Vines: lol
Jic: HAHAHA
Rukh 397: :FUWAhm:
PaakType: :FUWApat::MOCOpat::FUWApat::MOCOpat:
...
```
14 changes: 14 additions & 0 deletions examples/twitch.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2024 pyke.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::env::args;

use brainrot::{twitch, TwitchChat, TwitchChatEvent};
Expand Down
87 changes: 31 additions & 56 deletions examples/youtube.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,39 @@
use std::{future::IntoFuture, time::Duration};
// Copyright 2024 pyke.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use brainrot::youtube::{self, YouTubeChatPageProcessor};
use tokio::time::sleep;
use std::env::args;

use brainrot::youtube::{self, Action, ChatItem};
use futures_util::StreamExt;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let (options, cont) = youtube::get_options_from_live_page("J2YmJL0PX5M").await?;
let initial_chat = youtube::fetch_yt_chat_page(&options, &cont).await?;
if let Some(invalidation_continuation) = initial_chat.continuation_contents.as_ref().unwrap().live_chat_continuation.continuations[0]
.invalidation_continuation_data
.as_ref()
{
let topic = invalidation_continuation.invalidation_id.topic.to_owned();
let subscriber = youtube::SignalerChannel::new(topic).await?;
let (mut receiver, _handle) = subscriber.spawn_event_subscriber().await?;
tokio::spawn(async move {
let mut processor = YouTubeChatPageProcessor::new(initial_chat, &options).unwrap();
for msg in &processor {
println!("{}: {}", msg.author.display_name, msg.runs.iter().map(|c| c.to_string()).collect::<String>());
}

while receiver.recv().await.is_ok() {
match processor.cont().await {
Some(Ok(s)) => {
processor = s;
for msg in &processor {
println!("{}: {}", msg.author.display_name, msg.runs.iter().map(|c| c.to_string()).collect::<String>());
}

subscriber.refresh_topic(processor.signaler_topic.as_ref().unwrap()).await;
}
Some(Err(e)) => {
eprintln!("{e:?}");
break;
}
None => {
eprintln!("none");
break;
}
}
}
});
_handle.into_future().await.unwrap();
} else if let Some(timed_continuation) = initial_chat.continuation_contents.as_ref().unwrap().live_chat_continuation.continuations[0]
.timed_continuation_data
.as_ref()
{
let timeout = timed_continuation.timeout_ms as u64;
let mut processor = YouTubeChatPageProcessor::new(initial_chat, &options).unwrap();
loop {
for msg in &processor {
println!("{}: {}", msg.author.display_name, msg.runs.iter().map(|c| c.to_string()).collect::<String>());
}
sleep(Duration::from_millis(timeout as _)).await;
match processor.cont().await {
Some(Ok(e)) => processor = e,
_ => break
}
let context =
youtube::ChatContext::new_from_channel(args().nth(1).as_deref().unwrap_or("@miyukiwei"), youtube::ChannelSearchOptions::LatestLiveOrUpcoming).await?;
let mut stream = youtube::stream(&context).await?;
while let Some(Ok(c)) = stream.next().await {
if let Action::AddChatItem {
item: ChatItem::TextMessage { message_renderer_base, message },
..
} = c
{
println!(
"{}: {}",
message_renderer_base.author_name.unwrap().simple_text,
message.unwrap().runs.into_iter().map(|c| c.to_chat_string()).collect::<String>()
);
}
}
println!("???");
Ok(())
}
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2024 pyke.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[cfg(feature = "twitch")]
pub mod twitch;
#[cfg(feature = "twitch")]
Expand Down
14 changes: 14 additions & 0 deletions src/twitch/event.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2024 pyke.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{
collections::HashMap,
num::{NonZeroU16, NonZeroU32}
Expand Down
14 changes: 14 additions & 0 deletions src/twitch/identity.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2024 pyke.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// Represents a type that can be used to identify the client.
pub trait TwitchIdentity {
/// Converts this type into a tuple of `(username, Option<auth_key>)`.
Expand Down
14 changes: 14 additions & 0 deletions src/twitch/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2024 pyke.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{
pin::Pin,
task::{Context, Poll}
Expand Down
14 changes: 14 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2024 pyke.io
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

pub trait MapNonempty {
type T;

Expand Down
Loading

0 comments on commit 8927b82

Please sign in to comment.