+
- {{ data.label }}
+ {{ d.label }}
@@ -115,6 +134,8 @@
import LoadingTab from './components/loading-tab.vue';
import ErrorTab from './components/error-tab.vue';
+import {deep_copy} from 'utilities/object';
+
import displace from 'displacejs';
export default {
@@ -123,7 +144,7 @@ export default {
'loading-tab': LoadingTab
},
- props: ['tabs', 'room', 'raw_user', 'pos_x', 'pos_y', 'data', 'getZ', 'getFFZ'],
+ props: ['tabs', 'room', 'raw_user', 'pos_x', 'pos_y', 'data', 'ban_info', 'getZ', 'getFFZ'],
data() {
return {
@@ -133,10 +154,12 @@ export default {
loaded: false,
errored: false,
pinned: false,
+ twitch_banned: false,
user: null,
channel: null,
- self: null
+ self: null,
+ ban: null
}
},
@@ -155,6 +178,13 @@ export default {
return this.raw_user.displayName || this.raw_user.login;
},
+ subscription() {
+ if ( this.loaded )
+ return this.user.relationship?.cumulativeTenure;
+
+ return null;
+ },
+
userAge() {
if ( this.loaded )
return new Date(this.user.createdAt);
@@ -162,6 +192,11 @@ export default {
return null
},
+ followAge() {
+ const age = this.loaded && this.user?.relationship?.followedAt;
+ return age ? new Date(age) : null;
+ },
+
current_tab() {
return this.tabs[this.active_tab];
}
@@ -171,9 +206,10 @@ export default {
this.$emit('emit', ':open', this);
this.data.then(data => {
- this.user = data.data.targetUser;
- this.channel = data.data.channelUser;
- this.self = data.data.currentUser;
+ this.twitch_banned = data?.data?.activeTargetUser?.id !== data?.data?.targetUser?.id;
+ this.user = deep_copy(data?.data?.targetUser);
+ this.channel = deep_copy(data?.data?.channelUser);
+ this.self = deep_copy(data?.data?.currentUser);
this.loaded = true;
this.$emit('emit', ':load', this);
@@ -182,6 +218,14 @@ export default {
console.error(err); // eslint-disable-line no-console
this.errored = true;
});
+
+ this.ban_info.then(data => {
+ this.ban = deep_copy(data?.data?.chatRoomBanStatus);
+
+ }).catch(err => {
+ console.error(err);
+ this.ban = null;
+ });
},
mounted() {
@@ -236,6 +280,14 @@ export default {
this.pinned = true;
this.$emit('pin');
this.$emit('emit', ':pin', this);
+
+ this.cleanTips();
+ },
+
+ cleanTips() {
+ this.$nextTick(() => {
+ this.getFFZ().emit('tooltips:cleanup');
+ });
},
close() {
@@ -245,9 +297,10 @@ export default {
createDrag() {
this.$nextTick(() => {
this.displace = displace(this.$el, {
- handle: this.$el.querySelector('header'),
+ handle: this.$el.querySelector('.ffz-viewer-card__header'),
highlightInputs: true,
- constrain: true
+ constrain: true,
+ ignoreFn: e => e.target.closest('.viewer-card-drag-cancel') != null
});
})
},
diff --git a/src/modules/viewer_cards/get_ban_status.gql b/src/modules/viewer_cards/get_ban_status.gql
new file mode 100644
index 00000000..c2baa683
--- /dev/null
+++ b/src/modules/viewer_cards/get_ban_status.gql
@@ -0,0 +1,12 @@
+query FFZ_BanStatus($channelID: ID!, $targetUserID: ID!) {
+ chatRoomBanStatus(channelID: $channelID, userID: $targetUserID) {
+ createdAt
+ expiresAt
+ isPermanent
+ moderator {
+ id
+ login
+ displayName
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/viewer_cards/get_card_viewer.gql b/src/modules/viewer_cards/get_card_viewer.gql
new file mode 100644
index 00000000..2e6765ff
--- /dev/null
+++ b/src/modules/viewer_cards/get_card_viewer.gql
@@ -0,0 +1,79 @@
+query FFZ_ViewerCard($channelID: ID!, $targetID: ID, $targetLogin: String) {
+ activeTargetUser: user(id: $targetID, login: $targetLogin) {
+ id
+ }
+ targetUser: user(id: $targetID, login: $targetLogin, lookupType: ALL) {
+ id
+ login
+ displayName
+ bannerImageURL
+ profileImageURL(width: 70)
+ createdAt
+ profileViewCount
+ followers {
+ totalCount
+ }
+ ...friendButtonFragment
+ relationship(targetUserID: $channelID) {
+ cumulativeTenure: subscriptionTenure(tenureMethod: CUMULATIVE) {
+ daysRemaining
+ months
+ }
+ followedAt
+ subscriptionBenefit {
+ id
+ tier
+ purchasedWithPrime
+ gift {
+ isGift
+ }
+ }
+ }
+ }
+ channelUser: user(id: $channelID) {
+ id
+ login
+ displayName
+ subscriptionProducts {
+ id
+ name
+ price
+ url
+ emoteSetID
+ emotes {
+ id
+ }
+ tier
+ }
+ modLogsRoleAccess(role: MODERATOR) {
+ accessLevel
+ }
+ self {
+ banStatus {
+ isPermanent
+ }
+ isModerator
+ }
+ }
+ currentUser {
+ id
+ login
+ roles {
+ isSiteAdmin
+ isStaff
+ isGlobalMod
+ }
+ blockedUsers {
+ id
+ }
+ }
+}
+
+fragment friendButtonFragment on User {
+ id
+ self {
+ friendship {
+ __typename
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/viewer_cards/get_user_info.gql b/src/modules/viewer_cards/get_user_info.gql
deleted file mode 100644
index 6d54bb02..00000000
--- a/src/modules/viewer_cards/get_user_info.gql
+++ /dev/null
@@ -1,50 +0,0 @@
-query FFZ_ViewerCard($targetLogin: String!, $channelID: ID!) {
- targetUser: user(login: $targetLogin) {
- id
- login
- displayName
- bannerImageURL
- profileImageURL(width: 70)
- createdAt
- profileViewCount
- followers {
- totalCount
- }
- ...friendButtonFragment
- }
- channelUser: user(id: $channelID) {
- id
- login
- displayName
- subscriptionProducts {
- id
- price
- url
- emoteSetID
- emotes {
- id
- }
- }
- self {
- isModerator
- }
- }
- currentUser {
- id
- login
- roles {
- isSiteAdmin
- isStaff
- isGlobalMod
- }
- }
-}
-
-fragment friendButtonFragment on User {
- id
- self {
- friendship {
- __typename
- }
- }
-}
\ No newline at end of file
diff --git a/src/modules/viewer_cards/index.js b/src/modules/viewer_cards/index.js
index 43b915cf..fd1f00aa 100644
--- a/src/modules/viewer_cards/index.js
+++ b/src/modules/viewer_cards/index.js
@@ -7,7 +7,8 @@
import Module from 'utilities/module';
import {createElement} from 'utilities/dom';
-import GET_USER_INFO from './get_user_info.gql';
+import GET_CARD_VIEWER from './get_card_viewer.gql';
+import GET_BAN_STATUS from './get_ban_status.gql';
export default class ViewerCards extends Module {
constructor(...args) {
@@ -16,6 +17,7 @@ export default class ViewerCards extends Module {
this.inject('i18n');
this.inject('settings');
this.inject('site.apollo');
+ this.inject('site.twitch_data');
this.tabs = {};
@@ -71,7 +73,17 @@ export default class ViewerCards extends Module {
}
- async openCard(room, user, event) {
+ async openCard(room, user, msg, event) {
+ if ( typeof room === 'number' )
+ room = await this.twitch_data.getUser(room);
+ else if ( typeof room === 'string' )
+ room = await this.twitch_data.getUser(undefined, room);
+
+ if ( typeof user === 'number' )
+ user = await this.twitch_data.getUser(user);
+ else if ( typeof user === 'string' )
+ user = await this.twitch_data.getUser(undefined, user);
+
if ( user.userLogin && ! user.login )
user = {
login: user.userLogin,
@@ -79,6 +91,18 @@ export default class ViewerCards extends Module {
displayName: user.userDisplayName,
};
+ if ( ! room || (! room.id && ! room.login) )
+ return;
+
+ if ( ! user || (! user.id && ! user.login) )
+ return;
+
+ if ( ! user.id || ! user.login )
+ user = await this.twitch_data.getUser(user.id, user.login);
+
+ if ( ! room.id || ! room.login )
+ room = await this.twitch_data.getUser(room.id, room.login);
+
const old_card = this.open_cards[user.login];
if ( old_card ) {
old_card.$el.style.zIndex = ++this.last_z;
@@ -100,13 +124,21 @@ export default class ViewerCards extends Module {
// We start this first...
const user_info = this.apollo.client.query({
- query: GET_USER_INFO,
+ query: GET_CARD_VIEWER,
variables: {
targetLogin: user.login,
channelID: room.id
}
});
+ const ban_info = this.apollo.client.query({
+ query: GET_BAN_STATUS,
+ variables: {
+ targetUserID: user.id,
+ channelID: room.id
+ }
+ });
+
// But we only wait on loading Vue, since we can show a loading indicator.
await this.loadVue();
@@ -115,13 +147,14 @@ export default class ViewerCards extends Module {
room,
user,
user_info,
+ ban_info,
pos_x,
pos_y,
);
}
- buildCard(room, user, data, pos_x, pos_y) {
+ buildCard(room, user, data, ban_info, pos_x, pos_y) {
let child;
const component = new this.vue.Vue({
el: createElement('div'),
@@ -131,6 +164,7 @@ export default class ViewerCards extends Module {
room,
raw_user: user,
data,
+ ban_info,
getFFZ: () => this,
getZ: () => ++this.last_z
diff --git a/src/sites/twitch-twilight/modules/channel.js b/src/sites/twitch-twilight/modules/channel.js
index 821dfd9b..edffd389 100644
--- a/src/sites/twitch-twilight/modules/channel.js
+++ b/src/sites/twitch-twilight/modules/channel.js
@@ -41,14 +41,14 @@ export default class Channel extends Module {
}
});
- this.settings.add('channel.squads.no-autojoin', {
+ /*this.settings.add('channel.squads.no-autojoin', {
default: false,
ui: {
path: 'Channel > Behavior >> Squads',
title: 'Do not automatically redirect to Squad Streams.',
component: 'setting-check-box'
}
- });
+ });*/
this.ChannelPage = this.fine.define(
@@ -63,11 +63,11 @@ export default class Channel extends Module {
Twilight.CHAT_ROUTES
);
- this.SquadController = this.fine.define(
+ /*this.SquadController = this.fine.define(
'squad-controller',
n => n.onSquadPage && n.isValidSquad && n.handleLeaveSquad,
Twilight.CHAT_ROUTES
- );
+ );*/
}
@@ -76,8 +76,8 @@ export default class Channel extends Module {
this.RaidController.on('mount', this.wrapRaidController, this);
this.RaidController.on('update', this.noAutoRaids, this);
- this.SquadController.on('mount', this.noAutoSquads, this);
- this.SquadController.on('update', this.noAutoSquads, this);
+ //this.SquadController.on('mount', this.noAutoSquads, this);
+ //this.SquadController.on('update', this.noAutoSquads, this);
this.RaidController.ready((cls, instances) => {
for(const inst of instances)
diff --git a/src/sites/twitch-twilight/modules/channel_bar.jsx b/src/sites/twitch-twilight/modules/channel_bar.jsx
index 755e97ce..58265185 100644
--- a/src/sites/twitch-twilight/modules/channel_bar.jsx
+++ b/src/sites/twitch-twilight/modules/channel_bar.jsx
@@ -51,13 +51,6 @@ export default class ChannelBar extends Module {
n => n.getTitle && n.getGame && n.renderGame,
['user']
);
-
-
- this.HostBar = this.fine.define(
- 'host-container',
- n => n.handleReportHosterClick,
- ['user']
- )
}
onEnable() {
diff --git a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx
index 9148b7e1..6dd75bdf 100644
--- a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx
+++ b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx
@@ -13,6 +13,23 @@ import Module from 'utilities/module';
import SUB_STATUS from './sub_status.gql';
+const GLOBAL_SETS = [
+ 0,
+ 33,
+ 42
+];
+
+const POINTS_SETS = [
+ 300238151
+];
+
+const PRIME_SETS = [
+ 457,
+ 793,
+ 19151,
+ 19194
+];
+
const TIERS = {
1000: 'Tier 1',
2000: 'Tier 2',
@@ -541,7 +558,7 @@ export default class EmoteMenu extends Module {
let image;
if ( data.image )
- image = ();
+ image = ();
else
image = ();
@@ -827,7 +844,7 @@ export default class EmoteMenu extends Module {
this.handleObserve = this.handleObserve.bind(this);
this.pickTone = this.pickTone.bind(this);
this.clickTab = this.clickTab.bind(this);
- this.clickRefresh = this.clickRefresh.bind(this);
+ //this.clickRefresh = this.clickRefresh.bind(this);
this.handleFilterChange = this.handleFilterChange.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
@@ -946,7 +963,7 @@ export default class EmoteMenu extends Module {
});
}
- clickRefresh(event) {
+ /*clickRefresh(event) {
const target = event.currentTarget,
tt = target && target._ffz_tooltip$0;
@@ -978,7 +995,7 @@ export default class EmoteMenu extends Module {
Object.assign({}, this.state, {set_sets: sets, set_data: data, loading: false})
)));
});
- }
+ }*/
handleFilterChange(event) {
this.setState(this.filterState(event.target.value, this.state));
@@ -1006,18 +1023,10 @@ export default class EmoteMenu extends Module {
this.setState({loading: true}, () => {
t.getData(sets, force).then(d => {
- const promises = [];
-
- for(const set_id of sets)
- if ( ! has(d, set_id) )
- promises.push(t.emotes.awaitTwitchSetChannel(set_id))
-
- Promise.all(promises).then(() => {
- this.setState(this.filterState(this.state.filter, this.buildState(
- this.props,
- Object.assign({}, this.state, {set_sets: sets, set_data: d, loading: false})
- )));
- });
+ this.setState(this.filterState(this.state.filter, this.buildState(
+ this.props,
+ Object.assign({}, this.state, {set_sets: sets, set_data: d, loading: false})
+ )));
});
});
@@ -1188,7 +1197,7 @@ export default class EmoteMenu extends Module {
const sorter = this.getSorter(),
sort_tiers = t.chat.context.get('chat.emote-menu.sort-tiers-last'),
sort_emotes = (a,b) => {
- if ( a.inventory || b.inventory )
+ if ( a.misc || b.misc )
return sorter(a,b);
if ( ! a.locked && b.locked ) return -1;
@@ -1209,9 +1218,10 @@ export default class EmoteMenu extends Module {
const emote_sets = props.emote_data && props.emote_data.emoteSets,
emote_map = props.emote_data && props.emote_data.emoteMap,
twitch_favorites = t.emotes.getFavorites('twitch'),
- twitch_seen_favorites = new Set,
+ twitch_seen = new Set,
+
+ //twitch_seen_favorites = new Set,
- inventory = t.emotes.twitch_inventory_sets || new Set,
grouped_sets = {},
set_ids = new Set;
@@ -1221,58 +1231,58 @@ export default class EmoteMenu extends Module {
continue;
const set_id = parseInt(emote_set.id, 10),
- is_inventory = inventory.has(set_id),
- set_data = data[set_id] || {},
- more_data = t.emotes.getTwitchSetChannel(set_id, null, false),
- image = set_data.image,
- image_set = set_data.image_set;
+ owner = emote_set.owner,
+ is_points = owner?.login === 'channel_points',
+ chan = is_points ? null : owner,
+ set_data = data[set_id];
+
+ console.log('set', set_id, owner);
+
+ if ( chan )
+ t.emotes.setTwitchSetChannel(set_id, {
+ s_id: set_id,
+ c_id: chan.id,
+ c_name: chan.login,
+ c_title: chan.displayName
+ });
set_ids.add(set_id);
- let chan = set_data && set_data.user;
- if ( ! chan && more_data && more_data.c_id )
- chan = {
- id: more_data.c_id,
- login: more_data.c_name,
- display_name: more_data.c_name,
- bad: true
- };
-
let key = `twitch-set-${set_id}`,
sort_key = 0,
icon = 'twitch',
- title = chan && chan.display_name;
+ title = chan && (chan.displayName || chan.login);
if ( title )
- key = `twitch-${chan.id}`;
+ key = `twitch-${chan?.id}`;
- else {
- if ( is_inventory ) {
- title = t.i18n.t('emote-menu.inventory', 'Inventory');
- key = 'twitch-inventory';
- icon = 'inventory';
- sort_key = 50;
+ else if ( ! chan ) {
+ if ( is_points || POINTS_SETS.includes(set_id) ) {
+ title = t.i18n.t('emote-menu.points', 'Unlocked with Points');
+ key = 'twitch-points';
+ icon = 'channel-points';
+ sort_key = 45;
- } else if ( set_data && set_data.type === 'turbo' ) {
- title = t.i18n.t('emote-menu.prime', 'Prime');
+ } else if ( GLOBAL_SETS.includes(set_id) ) {
+ title = t.i18n.t('emote-menu.global', 'Global Emotes');
+ key = 'twitch-global';
+ sort_key = 100;
+
+ } else if ( PRIME_SETS.includes(set_id) ) {
+ title = t.i18n.t('emote_menu.prime', 'Prime');
+ key = 'twitch-prime';
icon = 'crown';
sort_key = 75;
- } else if ( more_data ) {
- title = more_data.c_name;
+ } else {
+ title = t.i18n.t('emote-menu.misc', 'Miscellaneous');
+ key = 'twitch-misc';
+ icon = 'inventory';
+ sort_key = 50;
+ }
- if ( title === '--global--' ) {
- title = t.i18n.t('emote-menu.global', 'Global Emotes');
- sort_key = 100;
-
- } else if ( title === '--twitch-turbo--' || title === 'turbo' || title === '--turbo-faces--' || title === '--prime--' || title === '--prime-faces--' ) {
- title = t.i18n.t('emote-menu.prime', 'Prime');
- icon = 'crown';
- sort_key = 75;
- }
- } else
- title = t.i18n.t('emote-menu.unknown-set', 'Set #{set_id}', {set_id})
- }
+ } else
+ title = t.i18n.t('emote-menu.unknown-set', 'Set #{set_id}', {set_id})
let section, emotes;
@@ -1280,32 +1290,33 @@ export default class EmoteMenu extends Module {
section = grouped_sets[key];
emotes = section.emotes;
- if ( chan && ! chan.bad && section.bad ) {
- section.title = title;
- section.image = image;
- section.image_set = image_set;
- section.icon = icon;
- section.sort_key = sort_key;
+ if ( set_data && section.bad ) {
section.bad = false;
+ section.renews = set_data.renews;
+ section.ends = set_data.ends;
+ section.prime = set_data.prime;
+ section.gift = set_data.gift;
}
} else {
emotes = [];
section = grouped_sets[key] = {
sort_key,
- bad: chan ? chan.bad : true,
key,
- image,
- image_set,
+ image: chan?.profileImageURL,
+ image_large: true,
icon,
title,
source: t.i18n.t('emote-menu.twitch', 'Twitch'),
emotes,
- renews: set_data.renews,
- ends: set_data.ends,
- prime: set_data.prime,
- gift: set_data.gift && set_data.gift.isGift
+ renews: set_data?.renews,
+ ends: set_data?.ends,
+ prime: set_data?.prime,
+ gift: set_data?.gift
}
+
+ if ( ! set_data )
+ section.bad = true;
}
for(const emote of emote_set.emotes) {
@@ -1339,15 +1350,17 @@ export default class EmoteMenu extends Module {
src,
srcSet,
overridden: overridden ? parseInt(mapped.id,10) : null,
- inventory: is_inventory,
+ misc: ! chan,
favorite: is_fav
};
+ t.emotes.setTwitchEmoteSet(id, set_id);
emotes.push(em);
- if ( is_fav && ! twitch_seen_favorites.has(id) ) {
+
+ if ( is_fav && ! twitch_seen.has(id) )
favorites.push(em);
- twitch_seen_favorites.add(id);
- }
+
+ twitch_seen.add(id);
}
if ( emotes.length ) {
@@ -1422,6 +1435,7 @@ export default class EmoteMenu extends Module {
const id = parseInt(emote.id, 10),
base = `${TWITCH_EMOTE_BASE}${id}`,
name = KNOWN_CODES[emote.token] || emote.token,
+ seen = twitch_seen.has(id),
is_fav = twitch_favorites.includes(id);
const em = {
@@ -1429,7 +1443,7 @@ export default class EmoteMenu extends Module {
id,
set_id,
name,
- locked,
+ locked: locked && ! seen,
src: `${base}/1.0`,
srcSet: `${base}/1.0 1x, ${base}/2.0 2x`,
favorite: is_fav
@@ -1437,10 +1451,10 @@ export default class EmoteMenu extends Module {
emotes.push(em);
- if ( ! locked && is_fav && ! twitch_seen_favorites.has(id) ) {
+ if ( ! locked && is_fav && ! seen )
favorites.push(em);
- twitch_seen_favorites.add(id);
- }
+
+ twitch_seen.add(id);
if ( lock_set )
lock_set.add(id);
@@ -1769,7 +1783,7 @@ export default class EmoteMenu extends Module {