Add hide blocked users in chat (#55)
Blocked users will be hidden from chat only if the logged in user is not a mod or not the channel owner.
This commit is contained in:
parent
a671a54d0d
commit
81c18feb45
8 changed files with 121 additions and 29 deletions
|
|
@ -25,6 +25,10 @@ abstract class _AuthBase with Store {
|
|||
@readonly
|
||||
UserTwitch? _user;
|
||||
|
||||
/// The user's list of blocked users.
|
||||
@readonly
|
||||
var _blockedUsers = ObservableList<UserBlockedTwitch>();
|
||||
|
||||
/// Whether the user is logged in or not.
|
||||
@readonly
|
||||
var _isLoggedIn = false;
|
||||
|
|
@ -40,7 +44,7 @@ abstract class _AuthBase with Store {
|
|||
_token = await _storage.read(key: 'USER_TOKEN');
|
||||
|
||||
// If the token does not exist, get the default token.
|
||||
// Otherwise, get the user info and log in.
|
||||
// Otherwise, log in.
|
||||
if (_token == null) {
|
||||
// Retrieve the currently stored default token if it exists.
|
||||
_token = await _storage.read(key: 'DEFAULT_TOKEN');
|
||||
|
|
@ -50,7 +54,12 @@ abstract class _AuthBase with Store {
|
|||
await _storage.write(key: 'DEFAULT_TOKEN', value: _token);
|
||||
}
|
||||
} else {
|
||||
// Get and update the current user's info.
|
||||
_user = await Twitch.getUserInfo(headers: headersTwitch);
|
||||
|
||||
// Get and update the current user's list of blocked users.
|
||||
if (_user?.id != null) _blockedUsers = (await Twitch.getUserBlockedList(id: _user!.id, headers: headersTwitch)).asObservable();
|
||||
|
||||
_isLoggedIn = true;
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +86,7 @@ abstract class _AuthBase with Store {
|
|||
'client_id': clientId,
|
||||
'redirect_uri': 'auth://',
|
||||
'response_type': 'token',
|
||||
'scope': 'chat:read chat:edit user:read:follows',
|
||||
'scope': 'chat:read chat:edit user:read:follows user:read:blocked_users user:manage:blocked_users',
|
||||
'force_verify': 'true',
|
||||
},
|
||||
);
|
||||
|
|
@ -96,6 +105,8 @@ abstract class _AuthBase with Store {
|
|||
// Retrieve the user info and set it
|
||||
_user = await Twitch.getUserInfo(headers: headersTwitch);
|
||||
|
||||
if (_user?.id != null) _blockedUsers = (await Twitch.getUserBlockedList(id: _user!.id, headers: headersTwitch)).asObservable();
|
||||
|
||||
// Set the login status to logged in.
|
||||
_isLoggedIn = true;
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,23 @@ mixin _$AuthStore on _AuthBase, Store {
|
|||
});
|
||||
}
|
||||
|
||||
final _$_blockedUsersAtom = Atom(name: '_AuthBase._blockedUsers');
|
||||
|
||||
ObservableList<UserBlockedTwitch> get blockedUsers {
|
||||
_$_blockedUsersAtom.reportRead();
|
||||
return super._blockedUsers;
|
||||
}
|
||||
|
||||
@override
|
||||
ObservableList<UserBlockedTwitch> get _blockedUsers => blockedUsers;
|
||||
|
||||
@override
|
||||
set _blockedUsers(ObservableList<UserBlockedTwitch> value) {
|
||||
_$_blockedUsersAtom.reportWrite(value, super._blockedUsers, () {
|
||||
super._blockedUsers = value;
|
||||
});
|
||||
}
|
||||
|
||||
final _$_isLoggedInAtom = Atom(name: '_AuthBase._isLoggedIn');
|
||||
|
||||
bool get isLoggedIn {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,19 @@ import 'package:frosty/api/irc_api.dart';
|
|||
|
||||
/// The object representation of a Twitch IRC message.
|
||||
class IRCMessage {
|
||||
final String raw;
|
||||
final Map<String, String> tags;
|
||||
final String? user;
|
||||
Command command;
|
||||
String? message;
|
||||
|
||||
IRCMessage({required this.tags, required this.command, required this.user, required this.message});
|
||||
IRCMessage({
|
||||
required this.raw,
|
||||
required this.tags,
|
||||
required this.command,
|
||||
required this.user,
|
||||
required this.message,
|
||||
});
|
||||
|
||||
/// Parses an IRC string and returns its corresponding [IRCMessage] object.
|
||||
factory IRCMessage.fromString(String whole) {
|
||||
|
|
@ -91,6 +98,7 @@ class IRCMessage {
|
|||
}
|
||||
|
||||
return IRCMessage(
|
||||
raw: whole,
|
||||
tags: mappedTags,
|
||||
command: messageCommand,
|
||||
user: user,
|
||||
|
|
@ -101,28 +109,50 @@ class IRCMessage {
|
|||
|
||||
/// The object representation of the IRC ROOMSTATE message.
|
||||
class ROOMSTATE {
|
||||
final String emoteOnly;
|
||||
final bool emoteOnly;
|
||||
final String followersOnly;
|
||||
final String r9k;
|
||||
final bool r9k;
|
||||
final String slowMode;
|
||||
final String subMode;
|
||||
final bool subMode;
|
||||
|
||||
const ROOMSTATE({
|
||||
this.emoteOnly = "0",
|
||||
this.followersOnly = "0",
|
||||
this.r9k = "0",
|
||||
this.slowMode = "0",
|
||||
this.subMode = "0",
|
||||
this.emoteOnly = false,
|
||||
this.followersOnly = '-1',
|
||||
this.r9k = false,
|
||||
this.slowMode = '0',
|
||||
this.subMode = false,
|
||||
});
|
||||
|
||||
/// Create a new copy with the parameters from the provided [IRCMessage]
|
||||
ROOMSTATE copyWith(IRCMessage ircMessage) {
|
||||
return ROOMSTATE(
|
||||
emoteOnly: ircMessage.tags['emote-only'] ?? emoteOnly,
|
||||
followersOnly: ircMessage.tags['followers-only'] ?? followersOnly,
|
||||
r9k: ircMessage.tags['r9k'] ?? r9k,
|
||||
slowMode: ircMessage.tags['slow'] ?? slowMode,
|
||||
subMode: ircMessage.tags['subs-only'] ?? subMode,
|
||||
);
|
||||
}
|
||||
ROOMSTATE fromIRC(IRCMessage ircMessage) => ROOMSTATE(
|
||||
emoteOnly: ircMessage.tags['emote-only'] == '0' ? false : true,
|
||||
followersOnly: ircMessage.tags['followers-only'] ?? followersOnly,
|
||||
r9k: ircMessage.tags['r9k'] == '0' ? false : true,
|
||||
slowMode: ircMessage.tags['slow'] ?? slowMode,
|
||||
subMode: ircMessage.tags['subs-only'] == '0' ? false : true,
|
||||
);
|
||||
}
|
||||
|
||||
class USERSTATE {
|
||||
final String? raw;
|
||||
final String color;
|
||||
final String displayName;
|
||||
final bool mod;
|
||||
final bool subscriber;
|
||||
|
||||
const USERSTATE({
|
||||
this.raw,
|
||||
this.color = '',
|
||||
this.displayName = '',
|
||||
this.mod = false,
|
||||
this.subscriber = false,
|
||||
});
|
||||
|
||||
USERSTATE fromIRC(IRCMessage ircMessage) => USERSTATE(
|
||||
raw: ircMessage.raw,
|
||||
color: ircMessage.tags['color'] ?? color,
|
||||
displayName: ircMessage.tags['display-name'] ?? displayName,
|
||||
mod: ircMessage.tags['mod'] == '0' ? false : true,
|
||||
subscriber: ircMessage.tags['subscriber'] == '0' ? false : true,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,3 +31,18 @@ class UserTwitch {
|
|||
|
||||
factory UserTwitch.fromJson(Map<String, dynamic> json) => _$UserTwitchFromJson(json);
|
||||
}
|
||||
|
||||
@JsonSerializable(createToJson: false, fieldRename: FieldRename.snake)
|
||||
class UserBlockedTwitch {
|
||||
final String userId;
|
||||
final String userLogin;
|
||||
final String displayName;
|
||||
|
||||
const UserBlockedTwitch(
|
||||
this.userId,
|
||||
this.userLogin,
|
||||
this.displayName,
|
||||
);
|
||||
|
||||
factory UserBlockedTwitch.fromJson(Map<String, dynamic> json) => _$UserBlockedTwitchFromJson(json);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,3 +18,10 @@ UserTwitch _$UserTwitchFromJson(Map<String, dynamic> json) => UserTwitch(
|
|||
json['view_count'] as int,
|
||||
json['created_at'] as String,
|
||||
);
|
||||
|
||||
UserBlockedTwitch _$UserBlockedTwitchFromJson(Map<String, dynamic> json) =>
|
||||
UserBlockedTwitch(
|
||||
json['user_id'] as String,
|
||||
json['user_login'] as String,
|
||||
json['display_name'] as String,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class ChatStats extends StatelessWidget {
|
|||
),
|
||||
ListTile(
|
||||
leading: const Text('Emote-only'),
|
||||
trailing: Text(chatStore.roomState.emoteOnly == '0' ? 'Disabled' : 'Enabled'),
|
||||
trailing: Text(chatStore.roomState.emoteOnly ? 'Enabled' : 'Disabled'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Text('Followers-only'),
|
||||
|
|
@ -33,7 +33,7 @@ class ChatStats extends StatelessWidget {
|
|||
),
|
||||
ListTile(
|
||||
leading: const Text('R9K'),
|
||||
trailing: Text(chatStore.roomState.r9k == '0' ? 'Disabled' : 'Enabled'),
|
||||
trailing: Text(chatStore.roomState.r9k ? 'Enabled' : 'Disabled'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Text('Slow'),
|
||||
|
|
@ -41,7 +41,7 @@ class ChatStats extends StatelessWidget {
|
|||
),
|
||||
ListTile(
|
||||
leading: const Text('Sub-only'),
|
||||
trailing: Text(chatStore.roomState.subMode == '0' ? 'Disabled' : 'Enabled'),
|
||||
trailing: Text(chatStore.roomState.subMode ? 'Enabled' : 'Disabled'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ abstract class _ChatStoreBase with Store {
|
|||
final SettingsStore settings;
|
||||
|
||||
/// The logged-in user's appearance in chat.
|
||||
String? _userState;
|
||||
var _userState = const USERSTATE();
|
||||
|
||||
/// Requested message to be sent by the user. Will only be sent on receival of a USERNOTICE command.
|
||||
IRCMessage? toSend;
|
||||
|
|
@ -112,6 +112,11 @@ abstract class _ChatStoreBase with Store {
|
|||
if (message.startsWith('@')) {
|
||||
final parsedIRCMessage = IRCMessage.fromString(message);
|
||||
|
||||
// Filter messages from any blocked users if not moderating and not the channel owner
|
||||
if (!_userState.mod &&
|
||||
channelName != auth.user?.login &&
|
||||
auth.blockedUsers.where((blockedUser) => blockedUser.userLogin == parsedIRCMessage.user).isNotEmpty) continue;
|
||||
|
||||
switch (parsedIRCMessage.command) {
|
||||
case Command.privateMessage:
|
||||
messages.add(parsedIRCMessage);
|
||||
|
|
@ -127,10 +132,10 @@ abstract class _ChatStoreBase with Store {
|
|||
messages.add(parsedIRCMessage);
|
||||
break;
|
||||
case Command.roomState:
|
||||
_roomState = _roomState.copyWith(parsedIRCMessage);
|
||||
_roomState = _roomState.fromIRC(parsedIRCMessage);
|
||||
continue;
|
||||
case Command.userState:
|
||||
_userState = message;
|
||||
_userState = _userState.fromIRC(parsedIRCMessage);
|
||||
if (toSend != null) {
|
||||
messages.add(toSend!);
|
||||
toSend = null;
|
||||
|
|
@ -197,8 +202,9 @@ abstract class _ChatStoreBase with Store {
|
|||
_channel.sink.add('PRIVMSG #$channelName :$message');
|
||||
|
||||
// Obtain the logged-in user's appearance in chat with USERSTATE and create the full message to render.
|
||||
if (_userState != null) {
|
||||
final userChatMessage = IRCMessage.fromString(_userState!);
|
||||
final userStateString = _userState.raw;
|
||||
if (userStateString != null) {
|
||||
final userChatMessage = IRCMessage.fromString(userStateString);
|
||||
userChatMessage.message = message;
|
||||
toSend = userChatMessage;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,13 @@ class VideoOverlay extends StatelessWidget {
|
|||
const SizedBox(height: 5.0),
|
||||
// SingleChildScrollView(scrollDirection: Axis.horizontal, child: Text(title)),
|
||||
// const SizedBox(height: 5.0),
|
||||
if (videoStore.streamInfo != null) Text('${NumberFormat().format(videoStore.streamInfo?.viewerCount)} viewers'),
|
||||
if (videoStore.streamInfo != null)
|
||||
Text(
|
||||
'${NumberFormat().format(videoStore.streamInfo?.viewerCount)} viewers',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFFFFFFF),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue