Skip to content

Commit

Permalink
feat: use twitch irc for sending messages (#1067)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevmo314 authored Sep 15, 2023
1 parent 1ab150a commit aec791b
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 49 deletions.
7 changes: 4 additions & 3 deletions lib/components/chat_history/twitch/raid_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:rtchat/components/chat_history/decorated_event.dart';
import 'package:rtchat/components/image/resilient_network_image.dart';
import 'package:rtchat/models/adapters/actions.dart';
import 'package:rtchat/models/channels.dart';
import 'package:rtchat/models/messages/twitch/event.dart';
import 'package:rtchat/models/messages/twitch/eventsub_configuration.dart';
import 'package:rtchat/models/user.dart';

class TwitchRaidEventWidget extends StatelessWidget {
final TwitchRaidEventModel model;
Expand Down Expand Up @@ -49,8 +49,9 @@ class TwitchRaidEventWidget extends StatelessWidget {
color:
Theme.of(context).buttonTheme.colorScheme?.primary))),
onTap: () {
ActionsAdapter.instance
.send(channel, "/shoutout ${model.from.login}");
final userModel =
Provider.of<UserModel>(context, listen: false);
userModel.send(channel, "/shoutout ${model.from.login}");
});
}),
]),
Expand Down
11 changes: 6 additions & 5 deletions lib/components/drawer/sidebar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:provider/provider.dart';
import 'package:rtchat/components/channel_search_bottom_sheet.dart';
import 'package:rtchat/components/drawer/quicklinks_listview.dart';
import 'package:rtchat/components/image/cross_fade_image.dart';
import 'package:rtchat/models/adapters/actions.dart';
import 'package:rtchat/models/audio.dart';
import 'package:rtchat/models/channels.dart';
import 'package:rtchat/models/layout.dart';
Expand Down Expand Up @@ -132,10 +131,12 @@ class _DrawerHeader extends StatelessWidget {
userChannel == model.activeChannel &&
userChannel != null
? (channel) {
ActionsAdapter.instance.send(
userChannel,
"/raid ${channel.displayName}",
);
final userModel =
Provider.of<UserModel>(
context,
listen: false);
userModel.send(userChannel,
"/raid ${channel.displayName}");
}
: null,
controller: controller,
Expand Down
11 changes: 3 additions & 8 deletions lib/components/message_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import 'package:provider/provider.dart';
import 'package:rtchat/components/autocomplete.dart';
import 'package:rtchat/components/emote_picker.dart';
import 'package:rtchat/components/image/resilient_network_image.dart';
import 'package:rtchat/models/adapters/actions.dart';
import 'package:rtchat/models/channels.dart';
import 'package:rtchat/models/commands.dart';
import 'package:rtchat/models/user.dart';

class MessageInputWidget extends StatefulWidget {
final Channel channel;
Expand Down Expand Up @@ -83,13 +83,8 @@ class _MessageInputWidgetState extends State<MessageInputWidget> {
await Future.wait([
() async {
try {
final error =
await ActionsAdapter.instance.send(widget.channel, value);
if (error != null) {
messenger.showSnackBar(SnackBar(
content: Text(error),
));
}
final userModel = Provider.of<UserModel>(context, listen: false);
await userModel.send(widget.channel, value);
} catch (e) {
messenger.showSnackBar(SnackBar(
content: Text(e.toString()),
Expand Down
20 changes: 0 additions & 20 deletions lib/models/adapters/actions.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:rtchat/models/channels.dart';

class ActionsAdapter {
Expand All @@ -14,25 +13,6 @@ class ActionsAdapter {
functions: FirebaseFunctions.instance);
static ActionsAdapter? _instance;

Future<String?> send(Channel channel, String message) async {
final call = functions.httpsCallable('send');
final key = firestore.collection('actions').doc().id;
for (var i = 0; i < 3; i++) {
try {
final result = await call({
"id": key,
"provider": channel.provider,
"channelId": channel.channelId,
"message": message,
});
return result.data;
} catch (e) {
FirebaseCrashlytics.instance.recordError(e, StackTrace.current);
}
}
throw Exception("Failed to send message");
}

Future<void> ban(Channel channel, String username) async {
final call = functions.httpsCallable('ban');
await call({
Expand Down
14 changes: 14 additions & 0 deletions lib/models/adapters/channels.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,18 @@ class ChannelsAdapter {
}
});
}

/// Returns the Twitch login for a given Twitch user ID. This is useful for
/// IRC which uses the login instead of the display name.
Future<String?> getLogin(Channel channel) async {
final doc = await db.collection("channels").doc(channel.toString()).get();
if (!doc.exists) {
return null;
}
final data = doc.data();
if (data == null) {
return null;
}
return data["login"];
}
}
26 changes: 26 additions & 0 deletions lib/models/adapters/tokens.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'dart:convert';

import 'package:cloud_firestore/cloud_firestore.dart';

class TokensAdapter {
final FirebaseFirestore db;

TokensAdapter._({required this.db});

static TokensAdapter get instance =>
_instance ??= TokensAdapter._(db: FirebaseFirestore.instance);
static TokensAdapter? _instance;

Future<String?> getAccessToken(
{required String userId, required String provider}) async {
final doc = await db.collection("tokens").doc(userId).get();
if (!doc.exists) {
return null;
}
final data = doc.data();
if (data == null || !data.containsKey(provider)) {
return null;
}
return jsonDecode(data[provider])['access_token'];
}
}
26 changes: 26 additions & 0 deletions lib/models/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:rtchat/models/adapters/channels.dart';
import 'package:rtchat/models/adapters/profiles.dart';
import 'package:rtchat/models/adapters/tokens.dart';
import 'package:rtchat/models/channels.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

class UserModel extends ChangeNotifier {
bool _isLoading = true;
Expand Down Expand Up @@ -87,4 +90,27 @@ class UserModel extends ChangeNotifier {

Future<UserCredential> signIn(String token) =>
FirebaseAuth.instance.signInWithCustomToken(token);

Future<void> send(Channel channel, String message) async {
final uid = _user?.uid;
final userChannel = _userChannel;
if (uid == null || userChannel == null) {
return;
}

final ws = WebSocketChannel.connect(
Uri.parse('wss://irc-ws.chat.twitch.tv:443'),
);

final token = await TokensAdapter.instance
.getAccessToken(userId: uid, provider: channel.provider);
final userLogin = await ChannelsAdapter.instance.getLogin(userChannel);
final channelLogin = await ChannelsAdapter.instance.getLogin(channel);

ws.sink.add('CAP REQ :twitch.tv/commands');
ws.sink.add('PASS oauth:$token');
ws.sink.add('NICK $userLogin');
ws.sink.add('PRIVMSG #$channelLogin :$message');
ws.sink.close();
}
}
42 changes: 29 additions & 13 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.17.2"
crypto:
dependency: "direct main"
description:
Expand Down Expand Up @@ -553,10 +553,10 @@ packages:
dependency: "direct main"
description:
name: intl
sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev"
source: hosted
version: "0.18.0"
version: "0.18.1"
js:
dependency: transitive
description:
Expand Down Expand Up @@ -625,18 +625,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.5.0"
meta:
dependency: transitive
description:
Expand Down Expand Up @@ -854,10 +854,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
stack_trace:
dependency: transitive
description:
Expand Down Expand Up @@ -926,10 +926,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.0"
typed_data:
dependency: transitive
description:
Expand Down Expand Up @@ -1066,6 +1066,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.1"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
web_socket_channel:
dependency: "direct main"
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
version: "2.4.0"
webview_flutter:
dependency: "direct main"
description:
Expand Down Expand Up @@ -1123,5 +1139,5 @@ packages:
source: hosted
version: "1.0.0"
sdks:
dart: ">=3.0.0 <4.0.0"
dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.10.0"
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dependencies:
flutter_custom_tabs: ^1.0.4
styled_text: ^7.0.0
barcode_scan2: ^4.2.4
web_socket_channel: ^2.4.0

dev_dependencies:
flutter_lints: ^2.0.2
Expand Down

0 comments on commit aec791b

Please sign in to comment.