From 0ff74cd0724299712500438d176300e1ee37b99b Mon Sep 17 00:00:00 2001 From: Cole Walker Date: Sun, 22 Nov 2020 09:33:58 -0500 Subject: [PATCH] feat: add send chat mutation * fix: :bug: update broken imports * feat: :sparkles: add send chat mutation * feat: :sparkles: tmi.js types added * fix: :bug: tmi wasn't in package.json --- README.md | 4 ++ package.json | 2 + src/channel-points/RedemptionListener.ts | 2 +- src/helpers/RefreshToken.ts | 22 ++++++- src/helpers/ServerSetup.ts | 2 +- src/schema/bit-pubsub-user-link-schema.ts | 2 +- src/schema/chat-pubsub-type-schema.ts | 36 ++++++++++- src/schema/chat-pubsub-user-link-schema.ts | 2 +- src/schema/follow-pubsub-type-schema.ts | 2 +- src/schema/game-type-schema.test.ts | 2 +- src/schema/redemption-pubsub-type-schema.ts | 4 +- ...redemption-pubsub-user-link-type-schema.ts | 4 +- src/schema/stream-type-schema.test.ts | 2 +- src/schema/stream-type-schema.ts | 3 +- src/schema/subscriber-type-schema.test.ts | 2 +- src/schema/subscriber-type-schema.ts | 2 +- src/schema/subscription-pubsub-type-schema.ts | 4 +- .../subscription-pubsub-user-link-schema.ts | 2 +- .../user-subscriber-link-type-schema.test.ts | 2 +- src/schema/user-type-schema.test.ts | 2 +- src/subscriptions/SubscriptionListener.ts | 2 +- yarn.lock | 63 ++++++++++++++++++- 22 files changed, 142 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 6f73726..456a758 100644 --- a/README.md +++ b/README.md @@ -552,6 +552,10 @@ type ChatUser { extend type Subscription { newChat(channel: String!): Chat } + +extend type Mutation { + sendChat(channel: String!, message: String!): Boolean +} ``` ### ChatUserLink diff --git a/package.json b/package.json index bc0d61c..8f75edb 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "graphql-tag": "^2.11.0", "graphql-tools": "^6.0.18", "reflect-metadata": "^0.1.13", + "tmi.js": "^1.5.0", "twitch": "^4.1.3", "twitch-chat-client": "^4.1.3", "twitch-pubsub-client": "^4.1.3" @@ -54,6 +55,7 @@ "@types/cors": "^2.8.7", "@types/express": "^4.17.7", "@types/jest": "^26.0.10", + "@types/tmi.js": "^1.4.2", "apollo-server": "^2.18.1", "apollo-server-testing": "^2.18.2", "concurrently": "^5.3.0", diff --git a/src/channel-points/RedemptionListener.ts b/src/channel-points/RedemptionListener.ts index bae7895..68b7c46 100644 --- a/src/channel-points/RedemptionListener.ts +++ b/src/channel-points/RedemptionListener.ts @@ -1,4 +1,4 @@ -import { PubSubClient } from 'twitch-pubsub-client/lib' +import { PubSubClient } from 'twitch-pubsub-client' export default async function (channelID: string, pubSubClient: PubSubClient) { return await pubSubClient.onRedemption(channelID, (message) => { diff --git a/src/helpers/RefreshToken.ts b/src/helpers/RefreshToken.ts index 7657eee..ab39f02 100644 --- a/src/helpers/RefreshToken.ts +++ b/src/helpers/RefreshToken.ts @@ -1,5 +1,5 @@ import axios from 'axios' -import { AuthProvider, StaticAuthProvider } from 'twitch/lib' +import { AuthProvider, StaticAuthProvider } from 'twitch' import { error } from 'console' require('dotenv').config() @@ -26,3 +26,23 @@ export default async ( return authProvider } + +export async function rawToken( + client_id?: string, + client_secret?: string, + refresh_token?: string +): Promise { + const clientId = client_id || process.env.USER_ID || '' + const clientSecret = client_secret || process.env.SECRET || '' + const refreshToken = refresh_token || process.env.REFRESH_TOKEN || '' + if (!clientId?.length || !clientSecret?.length || !refreshToken?.length) { + throw error('env not loading properly') + } + const raw = await axios.post( + `https://id.twitch.tv/oauth2/token?grant_type=refresh_token&refresh_token=${refreshToken}&client_id=${clientId}&client_secret=${clientSecret}` + ) + + const accessToken = raw.data.access_token + + return accessToken +} diff --git a/src/helpers/ServerSetup.ts b/src/helpers/ServerSetup.ts index 0dccc29..d50c62a 100644 --- a/src/helpers/ServerSetup.ts +++ b/src/helpers/ServerSetup.ts @@ -1,4 +1,4 @@ -import { ApiClient } from 'twitch/lib' +import { ApiClient } from 'twitch' import RefreshToken from './RefreshToken' import express from 'express' import { PubSub } from 'graphql-subscriptions' diff --git a/src/schema/bit-pubsub-user-link-schema.ts b/src/schema/bit-pubsub-user-link-schema.ts index 152b762..6854773 100644 --- a/src/schema/bit-pubsub-user-link-schema.ts +++ b/src/schema/bit-pubsub-user-link-schema.ts @@ -1,6 +1,6 @@ import { createModule, gql } from 'graphql-modules' import { PubSubBitsMessage } from 'twitch-pubsub-client' -import { ApiClient } from 'twitch/lib' +import { ApiClient } from 'twitch' import RefreshToken from '../helpers/RefreshToken' export const BitUserLinkResolvers = { Bit: { diff --git a/src/schema/chat-pubsub-type-schema.ts b/src/schema/chat-pubsub-type-schema.ts index e239bc7..d5e06fb 100644 --- a/src/schema/chat-pubsub-type-schema.ts +++ b/src/schema/chat-pubsub-type-schema.ts @@ -1,7 +1,8 @@ import { createModule, gql } from 'graphql-modules' import asyncify from 'callback-to-async-iterator' import { ChatClient } from 'twitch-chat-client' -import RefreshToken from '../helpers/RefreshToken' +import RefreshToken, { rawToken } from '../helpers/RefreshToken' +import tmi from 'tmi.js' export interface Chat { channel: string @@ -13,7 +14,7 @@ export const ChatPubSubResolvers = { Subscription: { newChat: { subscribe: async ( - _: any, + _: unknown, args: { channel: string }, { user_id, secret, refresh_token }: GraphQLModules.Context ) => { @@ -42,6 +43,33 @@ export const ChatPubSubResolvers = { }, }, }, + Mutation: { + sendChat: async ( + _: any, + args: { channel: string; message: string }, + { user_id, secret, refresh_token, twitch_id }: GraphQLModules.Context + ) => { + const password = await rawToken(user_id, secret, refresh_token) + + try { + const chatClient = tmi.Client({ + identity: { username: twitch_id, password }, + connection: { + reconnect: true, + }, + channels: [args.channel], + }) + await chatClient.connect() + await chatClient.say(args.channel, args.message) + await chatClient.disconnect() + } catch (err) { + console.error(err) + return false + } + + return true + }, + }, } export const ChatPubSubSchema = gql` @@ -68,6 +96,10 @@ export const ChatPubSubSchema = gql` extend type Subscription { newChat(channel: String!): Chat } + + extend type Mutation { + sendChat(channel: String!, message: String!): Boolean + } ` export const ChatPubSubModule = createModule({ diff --git a/src/schema/chat-pubsub-user-link-schema.ts b/src/schema/chat-pubsub-user-link-schema.ts index 55c9dba..e497895 100644 --- a/src/schema/chat-pubsub-user-link-schema.ts +++ b/src/schema/chat-pubsub-user-link-schema.ts @@ -1,5 +1,5 @@ import { createModule, gql } from 'graphql-modules' -import { ApiClient } from 'twitch/lib' +import { ApiClient } from 'twitch' import RefreshToken from '../helpers/RefreshToken' import { Chat } from './chat-pubsub-type-schema' diff --git a/src/schema/follow-pubsub-type-schema.ts b/src/schema/follow-pubsub-type-schema.ts index fb3a2f9..da56daf 100644 --- a/src/schema/follow-pubsub-type-schema.ts +++ b/src/schema/follow-pubsub-type-schema.ts @@ -1,5 +1,5 @@ import { createModule, gql } from 'graphql-modules' -import { ApiClient } from 'twitch/lib' +import { ApiClient } from 'twitch' import asyncify from 'callback-to-async-iterator' import RefreshToken from '../helpers/RefreshToken' import { HelixFollow } from 'twitch' diff --git a/src/schema/game-type-schema.test.ts b/src/schema/game-type-schema.test.ts index 7e4c93d..484ea19 100644 --- a/src/schema/game-type-schema.test.ts +++ b/src/schema/game-type-schema.test.ts @@ -20,7 +20,7 @@ import { validationMock, } from '../tests/mocks' import { StreamUserLinkModule } from './stream-user-link-type-schema' -import { ApiClient, HelixGame } from 'twitch/lib' +import { ApiClient, HelixGame } from 'twitch' import RefreshToken from '../helpers/RefreshToken' nock(`https://id.twitch.tv`) .post('/oauth2/token') diff --git a/src/schema/redemption-pubsub-type-schema.ts b/src/schema/redemption-pubsub-type-schema.ts index d571450..3b017fb 100644 --- a/src/schema/redemption-pubsub-type-schema.ts +++ b/src/schema/redemption-pubsub-type-schema.ts @@ -1,7 +1,7 @@ import { createModule, gql } from 'graphql-modules' import asyncify from 'callback-to-async-iterator' -import { PubSubClient } from 'twitch-pubsub-client/lib' -import { ApiClient } from 'twitch/lib' +import { PubSubClient } from 'twitch-pubsub-client' +import { ApiClient } from 'twitch' import RefreshToken from '../helpers/RefreshToken' export const RedemptionPubSubResolvers = { diff --git a/src/schema/redemption-pubsub-user-link-type-schema.ts b/src/schema/redemption-pubsub-user-link-type-schema.ts index c8b4182..175a728 100644 --- a/src/schema/redemption-pubsub-user-link-type-schema.ts +++ b/src/schema/redemption-pubsub-user-link-type-schema.ts @@ -1,6 +1,6 @@ import { createModule, gql } from 'graphql-modules' -import { PubSubRedemptionMessage } from 'twitch-pubsub-client/lib' -import { ApiClient } from 'twitch/lib' +import { PubSubRedemptionMessage } from 'twitch-pubsub-client' +import { ApiClient } from 'twitch' import RefreshToken from '../helpers/RefreshToken' export const RedemptionUserLinkResolvers = { diff --git a/src/schema/stream-type-schema.test.ts b/src/schema/stream-type-schema.test.ts index dd64cc9..036db54 100644 --- a/src/schema/stream-type-schema.test.ts +++ b/src/schema/stream-type-schema.test.ts @@ -17,7 +17,7 @@ import { authenticationMock, validationMock, } from '../tests/mocks' -import { ApiClient, HelixStream } from 'twitch/lib' +import { ApiClient, HelixStream } from 'twitch' import RefreshToken from '../helpers/RefreshToken' nock(`https://id.twitch.tv`) .post('/oauth2/token') diff --git a/src/schema/stream-type-schema.ts b/src/schema/stream-type-schema.ts index 79e82f2..939d6cb 100644 --- a/src/schema/stream-type-schema.ts +++ b/src/schema/stream-type-schema.ts @@ -1,6 +1,5 @@ import { createModule, gql } from 'graphql-modules' import { ApiClient, HelixStream, HelixStreamData } from 'twitch' -import { HelixStreamFilter } from 'twitch/lib/API/Helix/Stream/HelixStreamApi' import RefreshToken from '../helpers/RefreshToken' export const StreamResolvers = { @@ -23,7 +22,7 @@ export const StreamResolvers = { )?.map((x) => x.id) } - const streamFilter: HelixStreamFilter = { + const streamFilter: any = { ...args.streamFilter, game: args.streamFilter?.gameIds ? [...args.streamFilter?.gameIds, ...gameIds] diff --git a/src/schema/subscriber-type-schema.test.ts b/src/schema/subscriber-type-schema.test.ts index 851029f..74c3044 100644 --- a/src/schema/subscriber-type-schema.test.ts +++ b/src/schema/subscriber-type-schema.test.ts @@ -12,7 +12,7 @@ import { authenticationMock, validationMock, } from '../tests/mocks' -import { ApiClient, HelixSubscription } from 'twitch/lib' +import { ApiClient, HelixSubscription } from 'twitch' import RefreshToken from '../helpers/RefreshToken' nock(`https://id.twitch.tv`) diff --git a/src/schema/subscriber-type-schema.ts b/src/schema/subscriber-type-schema.ts index 8ecaeb9..dd0779b 100644 --- a/src/schema/subscriber-type-schema.ts +++ b/src/schema/subscriber-type-schema.ts @@ -1,6 +1,6 @@ import { createModule, gql } from 'graphql-modules' import { getLatestSub } from '../subscriptions/GetLatestSub' -import { ApiClient, HelixSubscription } from 'twitch/lib' +import { ApiClient, HelixSubscription } from 'twitch' import { getSubs } from '../subscriptions/GetSubs' import { getCurrentSubCount } from '../subscriptions/SubCount' import { getRandomSub } from '../subscriptions/GetRandomSub' diff --git a/src/schema/subscription-pubsub-type-schema.ts b/src/schema/subscription-pubsub-type-schema.ts index 1681dc6..a306e85 100644 --- a/src/schema/subscription-pubsub-type-schema.ts +++ b/src/schema/subscription-pubsub-type-schema.ts @@ -1,7 +1,7 @@ import { createModule, gql } from 'graphql-modules' import asyncify from 'callback-to-async-iterator' -import { PubSubClient } from 'twitch-pubsub-client/lib' -import { ApiClient } from 'twitch/lib' +import { PubSubClient } from 'twitch-pubsub-client' +import { ApiClient } from 'twitch' import RefreshToken from '../helpers/RefreshToken' export const SubscriptionPubSubResolvers = { diff --git a/src/schema/subscription-pubsub-user-link-schema.ts b/src/schema/subscription-pubsub-user-link-schema.ts index 65210d9..7616f75 100644 --- a/src/schema/subscription-pubsub-user-link-schema.ts +++ b/src/schema/subscription-pubsub-user-link-schema.ts @@ -1,6 +1,6 @@ import { createModule, gql } from 'graphql-modules' import { PubSubSubscriptionMessage } from 'twitch-pubsub-client' -import { ApiClient } from 'twitch/lib' +import { ApiClient } from 'twitch' import RefreshToken from '../helpers/RefreshToken' export const SubscriptionPubSubUserLinkResolvers = { diff --git a/src/schema/user-subscriber-link-type-schema.test.ts b/src/schema/user-subscriber-link-type-schema.test.ts index 903ba71..2edc058 100644 --- a/src/schema/user-subscriber-link-type-schema.test.ts +++ b/src/schema/user-subscriber-link-type-schema.test.ts @@ -21,7 +21,7 @@ import { HelixBroadcasterType, HelixUser, HelixUserType, -} from 'twitch/lib' +} from 'twitch' import RefreshToken from '../helpers/RefreshToken' nock(`https://id.twitch.tv`) .post('/oauth2/token') diff --git a/src/schema/user-type-schema.test.ts b/src/schema/user-type-schema.test.ts index 6fb1150..d328643 100644 --- a/src/schema/user-type-schema.test.ts +++ b/src/schema/user-type-schema.test.ts @@ -18,7 +18,7 @@ import { HelixFollow, HelixUser, HelixUserType, -} from 'twitch/lib' +} from 'twitch' import RefreshToken from '../helpers/RefreshToken' nock(`https://id.twitch.tv`) diff --git a/src/subscriptions/SubscriptionListener.ts b/src/subscriptions/SubscriptionListener.ts index a1726ce..57f7118 100644 --- a/src/subscriptions/SubscriptionListener.ts +++ b/src/subscriptions/SubscriptionListener.ts @@ -1,4 +1,4 @@ -import { PubSubClient } from 'twitch-pubsub-client/lib' +import { PubSubClient } from 'twitch-pubsub-client' import { getCurrentSubCount } from './SubCount' import { ApiClient } from 'twitch' diff --git a/yarn.lock b/yarn.lock index 81a58df..9d5e6da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1879,6 +1879,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== +"@types/tmi.js@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@types/tmi.js/-/tmi.js-1.4.2.tgz#0eeecb2665d60ccfe7cec9755c905d3022456975" + integrity sha512-EqIctOMPIleqMAnXeCPe7QYt4/kwKQJWxgi6/PbXxVL+Z1w+Ow64QuAahgCDt82XPtnn8fmjJ77XKsiFiLvooQ== + "@types/websocket@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.1.tgz#039272c196c2c0e4868a0d8a1a27bbb86e9e9138" @@ -4780,7 +4785,7 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.3: +har-validator@~5.1.0, har-validator@~5.1.3: version "5.1.5" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== @@ -7989,7 +7994,7 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.28: +psl@^1.1.24, psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== @@ -8024,6 +8029,11 @@ pumpify@^1.3.3: inherits "^2.0.3" pump "^2.0.0" +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -8389,6 +8399,32 @@ request-promise-native@^1.0.8: stealthy-require "^1.1.1" tough-cookie "^2.3.3" +request@2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" @@ -9369,6 +9405,14 @@ tiny-relative-date@^1.3.0: resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A== +tmi.js@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/tmi.js/-/tmi.js-1.5.0.tgz#746e7d66bafdc4eb23b4bb0b306a3287c637282b" + integrity sha512-JyWKy9dRkZDG1h6PnpE8fJVsTrW82/yANXoP7R3u02vG7PLCvHGRGTWzBwk0ymMJGX9A+YzDx5tXQDsTeJd/5A== + dependencies: + request "2.88.0" + ws "6.1.3" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -9450,6 +9494,14 @@ tough-cookie@^3.0.1: psl "^1.1.28" punycode "^2.1.1" +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + tr46@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" @@ -10158,6 +10210,13 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" +ws@6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.3.tgz#d2d2e5f0e3c700ef2de89080ebc0ac6e1bf3a72d" + integrity sha512-tbSxiT+qJI223AP4iLfQbkbxkwdFcneYinM2+x46Gx2wgvbaOMO36czfdfVUBRTHvzAMRhDd98sA5d/BuWbQdg== + dependencies: + async-limiter "~1.0.0" + ws@^5.2.0: version "5.2.2" resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f"