├── .gitignore ├── README.md ├── config.js ├── index.html ├── message.json ├── package.json ├── server.js └── youtubeService.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | tokens.json 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # youTubeChatBot 2 | ## API 账号设置 3 | 1. 前往 [Google API Console](https://console.developers.google.com/) 4 | 2. 创建一个新的**项目(Project)** 5 | 3. 从**库**中启用**YouTube Data API v3** 6 | 4. 前往**凭据**创建 **OAuth 客户端 ID**, 在**已获授权的重定向 URI**中填入```http://localhost:12000/callback``` 7 | 5. 如果没有创建过**OAuth 同意屏幕**的话则还需创建[**OAuth 同意屏幕**](https://console.cloud.google.com/apis/credentials/consent) 8 | 6. 从创建的客户端中获取**客户端ID(clientId)**, **已获授权的重定向 URI(redirectURI)** 和 **客户端密钥(clientSecret)** 并填入```config.js``` 9 | 10 | ## 前置条件 11 | Nodejs, Npm 12 | 13 | ## 安装 14 | ```npm install``` 15 | 16 | ## 使用 17 | - ```node server.js``` 18 | - 前往浏览器打开http://localhost:12000 19 | - **Authorize**登录你想发聊天的账号 20 | - **Get Active Chat**获取*蝗直播间聊天室id 21 | - **Start ChatBot** 开冲! 22 | - **Stop ChatBot** 停止复读,并保存聊天历史至```chat_{timestamp}.log```可供分析使用 23 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testLiveChatId : 'Cg0KC1QyZjJjZjFPNTVJKicKGFVDVHpJQkVMRHY5YUdlRXNqNERmTEI5QRILVDJmMmNmMU81NUk', 3 | clientId : '', 4 | clientSecret : '', 5 | redirectURI : 'http://localhost:12000/callback', 6 | messageFile: './message.json' 7 | } 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Youtube ChatBot 5 | 6 | 7 |

Youtube Chatbot

8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /message.json: -------------------------------------------------------------------------------- 1 | ["然后留张纸条:“饭在锅里,我在床上”这么暧昧的言语来暗示老公。","当然梦颖是不会这样的,她是真累了才留了纸条。","我吃了饭,爬到床上,梦颖已经睡着了,发出细细的喘气声,","我们已经1个多星期没做了,我突然有点想要,","我想把她拍醒,但是手还是收回来了。不忍心打扰她,","毕竟她付出很多了。但是我憋得难受,想了一晚上,","还是准备第二天早上再找老婆解决,反正第二天公司安排我休息。","早上,我早早的醒来,看到身边的美妻,薄薄的睡衣下黑色的胸罩若隐若现,","7月是个最忙的季节,公司开始整体系统维护升级,我负责的项目又是最多的,所以公司才会在季度维护后安排人出去玩,","我就被分到了一个名额。每次都要加班到11点才能回家,梦颖很体贴,我加班回家她总是提前把饭热好,","we don’t need any Korean! !","\"I...called a cell?\"","他所租的是专门分租给学生的一层楼,在旧公寓六楼顶木板加盖的小违建,一共有六个间,共用一套卫浴设备和一小间厨房,外头屋顶还留有一小片阳台可以晒衣服。","sublet exclusively to students,","sublet exclusively to students,","This liquid seems to be called water.","This liquid seems to be called water.","This liquid seems to be called water.","Barbara chan, Cosette chan, and Kiryu Coco were all lost in the desert. They found a lamp and rubbed it. A genie popped out and granted them each one wish.","Barbara chan wished to be back home. P o o f! She was back home. Cosette chan wished to be at home with her family. P o o f! She was back home with her family.","Kiryu Coco said, \"Fuck you and never come back! \":cocobitte:","Futaba chan wished to be back home. P o o f! She was back home. Cosette chan wished to be at home with her family. P o o f! She was back home with her family.","To measure the cost of environmental degradation, we introduced the following variables:","Kiryu Coco said, \"Fuck you and never come back! \":cocobitte:","目录 : 1-7 第1章、 春色满园 第2章、 表弟邪念 第3章、 长途汽车 第4章、 山野寻欢 第5章、 暴露女友之暖香阁 第06-08、 暴露女友之豪门夜宴 第09章、 暴露女友之化妆舞会 第10-11章、暴露女友之温泉旅行 第12章、 暴露女友之香艳准媳妇 第13-15章、暴露女友之胁迫 第16-18章、暴露女友之淫语霏霏 第19-20章、暴露女友之杂记两则(一)春光乍泄(二)芭蕾舞 第21","2012","Yukari chan wished to be back home. P o o f! She was back home. Cosette chan wished to be at home with her family. P o o f! She was back home with her family.","Kiryu Coco said, \"Fuck you and never come back! \":cocobitte:","Team#1909647 Page 8 of 18","-31章、暴露女友之冬夜爱歌 第32-33章、暴露女友之旧情馀孽 第34章、暴露女友之性感风波 第35章、暴露女友之湿润假期 第36章、暴露女友之小倩的情色按摩 第37章、暴露女友之小倩醉后的身份(第一部)清蕊含香 第38章、暴露女友之小倩醉后的身份(第二部)凌乱的天使 第39章、暴露女友之杂记13篇 1湿身娇娃 2准媳妇诱惑 3俏女儿 4更衣意外5浴场春光 1.","Fuuka chan, Futaba chan, and Kiryu Coco were all lost in the desert. They found a lamp and rubbed it. A genie popped out and granted them each one wish.","Fuuka chan wished to be back home. P o o f! She was back home. Futaba chan wished to be at home with her family. P o o f! She was back home with her family.","Competition answer paper","Kiryu Coco said, \"Fuck you and never come back! \":cocobitte:","Competition answer paper","小倩日记 1 艳福老爸 2 表姐宿舍 3病如西子4.公车偷拍 1公园春色 2出浴女神 3车震 4不良遭遇 1、校外旅馆2,","Hifumi chan wished to be back home. P o o f! She was back home. Ann chan wished to be at home with her family. P o o f! She was back home with her family.","engineering technology, modern science in the new issue. 2. generally, there is a definite practical problem. Two, some assumptions are as follows: 1. only the process and rules of qualitative assumpt","Kiryu Coco said, \"Fuck you and never come back! \":cocobitte:","The unit cost of industrial solid waste:","Marie chan wished to be back home. P o o f! She was back home. Chie chan wished to be at home with her family. P o o f! She was back home with her family.","1, the water environment mathematical model,","Riley chan wished to be back home. P o o f! She was back home. Cosette chan wished to be at home with her family. P o o f! She was back home with her family.","To ensure the integrity and the sustainable development of ecological system is the premise and foundation of development, If we simply pursue the speed of development and neglect the pressure that co","Yukari chan wished to be back home. P o o f! She was back home. Riley chan wished to be at home with her family. P o o f! She was back home with her family.","Alice chan wished to be back home. P o o f! She was back home. Fuuka chan wished to be at home with her family. P o o f! She was back home with her family.","Ryza chan wished to be back home. P o o f! She was back home. Cosette chan wished to be at home with her family. P o o f! She was back home with her family.","Barbara chan wished to be back home. P o o f! She was back home. Ann chan wished to be at home with her family. P o o f! She was back home with her family.","Cosette chan wished to be back home. P o o f! She was back home. Marie chan wished to be at home with her family. P o o f! She was back home with her family.","Alice chan wished to be back home. P o o f! She was back home. Marie chan wished to be at home with her family. P o o f! She was back home with her family.","1.1 电影院 相信绝大多数朋友都有带女友看电影的经历。刚开始交往时就只是单纯看电 影,但随着关係的深入,","The cost of treatment of each pollutant is:","Apart from seizure of land by wars, Qin acquired through their bribery towns in minor cases and cities in major cases. These, put together, were a hundred times more than those occupied through its vi","看电影这三个字对情侣们就没那麽简单了。 在那个漆黑的环境裡,谈情说爱卿卿我我是免不了的,毛手毛脚更是不可缺 少的部分。","Total / 10,000 yuan Team#1909647 Page 15 of 18","Virtual cost of living air pollution/10,000 yuan 2010 23,070,658.90 2011 23,075,863.74 2012 23,146,010.04 2013 23,014,009.95 2014 23,051,302.18 2015 22,754,553.94","更有甚者,有的情侣会在电影院裡做爱。 那种新鲜刺激的感觉让我羡慕不已,但我的女友小倩是比较保守的女孩,","Unit governance cost of disposal Unit governance cost of","Discrete system simulation - there is a set of state variables. Continuous system simulation with analytic expression or system structure diagram. 2. factor test method -- local test on the system, th","平 时穿的内衣都是普通的胸罩和少女内裤,刚开始交往时连牵手都会害羞。 后来经过我长时间的调教,把她的身体弄得很敏感,","Dust governance cost=b×b2×b1","只要是我,随便弄几下 就会让她服服帖帖,只懂闭眼喘息。 但她终究是保守的女孩,在电影院裡做爱这种事她是坚决不会同意的。","Team#1909647 Page 13 of 18","natural gas use ratio c2 Liquefied petroleum gas use ratio c3 Per capita artificial gas usage d1 Per capita natural gas usage d2","虽然如此,对于适度的亲热她还是不会反对的,加上我爱抚的功夫,只要停 留在做爱底线以外,对于我的侵犯她只能听之任之。","Team#1909647 Page 5 of 18","Team#1909647 Page 5 of 18","这天下午我们又来到电影院。 这裡有两种放映厅,一种是很大的,专门放一些赚钱的大片,裡面的座位都 是普通的单人座位。","4. Conclusions","China is a representative country. Therefore, the sample is representative and persuasive.","另一种是稍微小一些的放映厅,放的都是一些边缘影片,影院也知道来这裡 的人主要目的不在电影上。 裡面的座位都是双人雅座,","1、Where speeds are high special cooling arrangements become necessary which may increase frictional drag .","其实就是长一些的椅子,可以容纳三个人,两边 和背后的挡板都很高,可以挡住别人的视线。 我当然想进这样的地方,","要跟女友好好亲热一番才值回票价。 但还是装作犹豫徵询女友的意见。 见她对小放映厅的几部电影有兴趣,就顺水推船拉着她的手走了进去。","wiebitte","然后留张纸条:“饭在锅里,我在床上”这么暧昧的言语来暗示老公。","当然梦颖是不会这样的,她是真累了才留了纸条。","我吃了饭,爬到床上,梦颖已经睡着了,发出细细的喘气声,","我们已经1个多星期没做了,我突然有点想要,","我想把她拍醒,但是手还是收回来了。不忍心打扰她,","毕竟她付出很多了。但是我憋得难受,想了一晚上,","还是准备第二天早上再找老婆解决,反正第二天公司安排我休息。","早上,我早早的醒来,看到身边的美妻,薄薄的睡衣下黑色的胸罩若隐若现,","7月是个最忙的季节,公司开始整体系统维护升级,我负责的项目又是最多的,所以公司才会在季度维护后安排人出去玩,","我就被分到了一个名额。每次都要加班到11点才能回家,梦颖很体贴,我加班回家她总是提前把饭热好,","we don’t need any Korean! !","\"I...called a cell?\"","他所租的是专门分租给学生的一层楼,在旧公寓六楼顶木板加盖的小违建,一共有六个间,共用一套卫浴设备和一小间厨房,外头屋顶还留有一小片阳台可以晒衣服。","sublet exclusively to students,","sublet exclusively to students,","This liquid seems to be called water.","This liquid seems to be called water.","This liquid seems to be called water.","Barbara chan, Cosette chan, and Kiryu Coco were all lost in the desert. They found a lamp and rubbed it. A genie popped out and granted them each one wish.","Barbara chan wished to be back home. P o o f! She was back home. Cosette chan wished to be at home with her family. P o o f! She was back home with her family.","Kiryu Coco said, \"Fuck you and never come back! \":cocobitte:","Futaba chan wished to be back home. P o o f! She was back home. Cosette chan wished to be at home with her family. P o o f! She was back home with her family.","To measure the cost of environmental degradation, we introduced the following variables:","Kiryu Coco said, \"Fuck you and never come back! \":cocobitte:","目录 : 1-7 第1章、 春色满园 第2章、 表弟邪念 第3章、 长途汽车 第4章、 山野寻欢 第5章、 暴露女友之暖香阁 第06-08、 暴露女友之豪门夜宴 第09章、 暴露女友之化妆舞会 第10-11章、暴露女友之温泉旅行 第12章、 暴露女友之香艳准媳妇 第13-15章、暴露女友之胁迫 第16-18章、暴露女友之淫语霏霏 第19-20章、暴露女友之杂记两则(一)春光乍泄(二)芭蕾舞 第21","2012","Yukari chan wished to be back home. P o o f! She was back home. Cosette chan wished to be at home with her family. P o o f! She was back home with her family.","Kiryu Coco said, \"Fuck you and never come back! \":cocobitte:","Team#1909647 Page 8 of 18","-31章、暴露女友之冬夜爱歌 第32-33章、暴露女友之旧情馀孽 第34章、暴露女友之性感风波 第35章、暴露女友之湿润假期 第36章、暴露女友之小倩的情色按摩 第37章、暴露女友之小倩醉后的身份(第一部)清蕊含香 第38章、暴露女友之小倩醉后的身份(第二部)凌乱的天使 第39章、暴露女友之杂记13篇 1湿身娇娃 2准媳妇诱惑 3俏女儿 4更衣意外5浴场春光 1.","Fuuka chan, Futaba chan, and Kiryu Coco were all lost in the desert. They found a lamp and rubbed it. A genie popped out and granted them each one wish.","Fuuka chan wished to be back home. P o o f! She was back home. Futaba chan wished to be at home with her family. P o o f! She was back home with her family.","Competition answer paper","Kiryu Coco said, \"Fuck you and never come back! \":cocobitte:","Competition answer paper","小倩日记 1 艳福老爸 2 表姐宿舍 3病如西子4.公车偷拍 1公园春色 2出浴女神 3车震 4不良遭遇 1、校外旅馆2,","Hifumi chan wished to be back home. P o o f! She was back home. Ann chan wished to be at home with her family. P o o f! She was back home with her family.","engineering technology, modern science in the new issue. 2. generally, there is a definite practical problem. Two, some assumptions are as follows: 1. only the process and rules of qualitative assumpt","Kiryu Coco said, \"Fuck you and never come back! \":cocobitte:","The unit cost of industrial solid waste:","Marie chan wished to be back home. P o o f! She was back home. Chie chan wished to be at home with her family. P o o f! She was back home with her family.","1, the water environment mathematical model,","Riley chan wished to be back home. P o o f! She was back home. Cosette chan wished to be at home with her family. P o o f! She was back home with her family.","To ensure the integrity and the sustainable development of ecological system is the premise and foundation of development, If we simply pursue the speed of development and neglect the pressure that co","Yukari chan wished to be back home. P o o f! She was back home. Riley chan wished to be at home with her family. P o o f! She was back home with her family.","Alice chan wished to be back home. P o o f! She was back home. Fuuka chan wished to be at home with her family. P o o f! She was back home with her family.","Ryza chan wished to be back home. P o o f! She was back home. Cosette chan wished to be at home with her family. P o o f! She was back home with her family.","Barbara chan wished to be back home. P o o f! She was back home. Ann chan wished to be at home with her family. P o o f! She was back home with her family.","Cosette chan wished to be back home. P o o f! She was back home. Marie chan wished to be at home with her family. P o o f! She was back home with her family.","Alice chan wished to be back home. P o o f! She was back home. Marie chan wished to be at home with her family. P o o f! She was back home with her family.","1.1 电影院 相信绝大多数朋友都有带女友看电影的经历。刚开始交往时就只是单纯看电 影,但随着关係的深入,","The cost of treatment of each pollutant is:","Apart from seizure of land by wars, Qin acquired through their bribery towns in minor cases and cities in major cases. These, put together, were a hundred times more than those occupied through its vi","看电影这三个字对情侣们就没那麽简单了。 在那个漆黑的环境裡,谈情说爱卿卿我我是免不了的,毛手毛脚更是不可缺 少的部分。","Total / 10,000 yuan Team#1909647 Page 15 of 18","Virtual cost of living air pollution/10,000 yuan 2010 23,070,658.90 2011 23,075,863.74 2012 23,146,010.04 2013 23,014,009.95 2014 23,051,302.18 2015 22,754,553.94","更有甚者,有的情侣会在电影院裡做爱。 那种新鲜刺激的感觉让我羡慕不已,但我的女友小倩是比较保守的女孩,","Unit governance cost of disposal Unit governance cost of","Discrete system simulation - there is a set of state variables. Continuous system simulation with analytic expression or system structure diagram. 2. factor test method -- local test on the system, th","平 时穿的内衣都是普通的胸罩和少女内裤,刚开始交往时连牵手都会害羞。 后来经过我长时间的调教,把她的身体弄得很敏感,","Dust governance cost=b×b2×b1","只要是我,随便弄几下 就会让她服服帖帖,只懂闭眼喘息。 但她终究是保守的女孩,在电影院裡做爱这种事她是坚决不会同意的。","Team#1909647 Page 13 of 18","natural gas use ratio c2 Liquefied petroleum gas use ratio c3 Per capita artificial gas usage d1 Per capita natural gas usage d2","虽然如此,对于适度的亲热她还是不会反对的,加上我爱抚的功夫,只要停 留在做爱底线以外,对于我的侵犯她只能听之任之。","Team#1909647 Page 5 of 18","Team#1909647 Page 5 of 18","这天下午我们又来到电影院。 这裡有两种放映厅,一种是很大的,专门放一些赚钱的大片,裡面的座位都 是普通的单人座位。","4. Conclusions","China is a representative country. Therefore, the sample is representative and persuasive.","另一种是稍微小一些的放映厅,放的都是一些边缘影片,影院也知道来这裡 的人主要目的不在电影上。 裡面的座位都是双人雅座,","1、Where speeds are high special cooling arrangements become necessary which may increase frictional drag .","其实就是长一些的椅子,可以容纳三个人,两边 和背后的挡板都很高,可以挡住别人的视线。 我当然想进这样的地方,","要跟女友好好亲热一番才值回票价。 但还是装作犹豫徵询女友的意见。 见她对小放映厅的几部电影有兴趣,就顺水推船拉着她的手走了进去。","wiebitte","This liquid seems to be called water.","4. Conclusions","这样的放映厅不是对号入座的,我拉着女友找座位,来到靠前的位置发现了 一个空座位,夹在其他两个座位中间。","This liquid seems to be called water.","4. Conclusions","这样的放映厅不是对号入座的,我拉着女友找座位,来到靠前的位置发现了 一个空座位,夹在其他两个座位中间。","早上,我早早的醒来,看到身边的美妻,薄薄的睡衣下黑色的胸罩若隐若现,","我们走了进去,经过靠近过道的座位时看到一个三十岁走有的男人座在那裡 . 我想可能是他的女友去洗手间了,","1、Where speeds are high special cooling arrangements become necessary which may increase frictional drag .","不然哪有一个人跑到这裡来看电影的。 我跟女友坐在他隔壁的位置,因为这裡比较靠近中央,视角更好。 对于那个男人,","머리에 뿔은 왜달림? 황소같네ᄏᄏᄏ","여기여기!","님들 원신 몇렙?","아이폰 12 가성비 어떰?","한국어 좀 자제해라, 창피하지도 않?","인공지능임? 아니면 사람임?","머리에 뿔은 왜달림? 황소같네ᄏᄏᄏ","여기여기!","님들 원신 몇렙?","아이폰 12 가성비 어떰?","한국어 좀 자제해라, 창피하지도 않?","인공지능임? 아니면 사람임?","머리에 뿔은 왜달림? 황소같네ᄏᄏᄏ","여기여기!","님들 원신 몇렙?","아이폰 12 가성비 어떰?","한국어 좀 자제해라, 창피하지도 않?","인공지능임? 아니면 사람임?","머리에 뿔은 왜달림? 황소같네ᄏᄏᄏ","여기여기!","님들 원신 몇렙?","아이폰 12 가성비 어떰?","한국어 좀 자제해라, 창피하지도 않?","인공지능임? 아니면 사람임?","머리에 뿔은 왜달림? 황소같네ᄏᄏᄏ","여기여기!","“干娘休要去,奴酒不多用了。”婆子便道:“阿呀!娘子,大官人又不是别人,没事相陪吃一盏儿,怕怎的!”妇人口里说“不用了”坐着却不动身。","闻道今年初避人,珊珊镜挂长随身。愿得侍儿为道意,后堂罗帐一相亲。","这妇人见王婆去了,倒把椅儿扯开一边坐着,却只偷眼睃看。西门庆坐在对面,一径把那双涎瞪瞪的眼睛看着他,便又问道:“却才到忘了问娘子尊姓?”妇人便低着头带笑的回道:“姓武。”西门庆故做不听得,说道:“姓堵?","诗曰:璇闺绣户斜光入,千金女儿倚门立。横波美目虽后来,罗袜遥遥不相及。","婆子一面把门拽上,用索儿拴了,倒关他二人在屋里。当路坐了,一头续着锁。","话说王婆拿银子出门,便向妇人满面堆下笑来,说道:“老身去那街上取瓶儿来,有劳娘子相待官人坐一坐。壶里有酒,没便再筛两盏儿,且和大官人吃着,老身直去县东街,那里有好酒买一瓶来,有好一歇儿耽搁。”妇人听了说:","님들 원신 몇렙?","11111111111111","11111111111111","아이폰 12 가성비 어떰?","11111111111111","11111111111111","한국어 좀 자제해라, 창피하지도 않?","11111111111111","11111111111111","11111111111111","11111111111111","诗曰:璇闺绣户斜光入,千金女儿倚门立。横波美目虽后来,罗袜遥遥不相及。","闻道今年初避人,珊珊镜挂长随身。愿得侍儿为道意,后堂罗帐一相亲。","话说王婆拿银子出门,便向妇人满面堆下笑来,说道:“老身去那街上取瓶儿来,有劳娘子相待官人坐一坐。壶里有酒,没便再筛两盏儿,且和大官人吃着,老身直去县东街,那里有好酒买一瓶来,有好一歇儿耽搁。”妇人听了说:","“干娘休要去,奴酒不多用了。”婆子便道:“阿呀!娘子,大官人又不是别人,没事相陪吃一盏儿,怕怎的!”妇人口里说“不用了”坐着却不动身。","婆子一面把门拽上,用索儿拴了,倒关他二人在屋里。当路坐了,一头续着锁。","11111111111111","인공지능임? 아니면 사람임?","머리에 뿔은 왜달림? 황소같네ᄏᄏᄏ","여기여기!","님들 원신 몇렙?","아이폰 12 가성비 어떰?","한국어 좀 자제해라, 창피하지도 않?","인공지능임? 아니면 사람임?","머리에 뿔은 왜달림? 황소같네ᄏᄏᄏ","여기여기!","님들 원신 몇렙?","아이폰 12 가성비 어떰?","한국어 좀 자제해라, 창피하지도 않?","인공지능임? 아니면 사람임?","머리에 뿔은 왜달림? 황소같네ᄏᄏᄏ","여기여기!","님들 원신 몇렙?","아이폰 12 가성비 어떰?","한국어 좀 자제해라, 창피하지도 않?","인공지능임? 아니면 사람임?","머리에 뿔은 왜달림? 황소같네ᄏᄏᄏ","여기여기!","님들 원신 몇렙?","아이폰 12 가성비 어떰?","한국어 좀 자제해라, 창피하지도 않?","인공지능임? 아니면 사람임?","a","11111111111111","한국어 좀 자제해라, 창피하지도 않?","1.1 电影院 相信绝大多数朋友都有带女友看电影的经历。刚开始交往时就只是单纯看电 影,但随着关係的深入,","님들 원신 몇렙?","아이폰 12 가성비 어떰?"] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtubechatbot", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.17.1", 13 | "googleapis": "^61.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { chat } = require('googleapis/build/src/apis/chat'); 3 | const path = require('path'); 4 | 5 | const youtubeService = require('./youtubeService.js'); 6 | 7 | const server = express(); 8 | 9 | server.get('/', (req, res) => 10 | res.sendFile(path.join(__dirname + '/index.html')) 11 | ); 12 | 13 | server.get('/authorize', (request, response) => { 14 | youtubeService.getCode(response); 15 | }); 16 | 17 | server.get('/callback', (req, response) => { 18 | const { code } = req.query; 19 | youtubeService.getTokensWithCode(code); 20 | response.redirect('/'); 21 | }); 22 | 23 | server.get('/init-active-chat/:channelId', async (req, res) => { 24 | let channelId = req.params.channelId 25 | let chatId = await youtubeService.findActiveChat(channelId); 26 | res.redirect(chatId.found ? 200 : 404, '/'); 27 | }); 28 | 29 | server.get('/start-chat-bot', (req, res) => { 30 | youtubeService.startChatBot(); 31 | res.redirect('/'); 32 | }); 33 | 34 | server.get('/stop-chat-bot', (req, res) => { 35 | youtubeService.stopChatBot(); 36 | res.redirect('/'); 37 | }); 38 | 39 | server.listen(12000, function() { 40 | console.log('Server is Ready'); 41 | }); 42 | -------------------------------------------------------------------------------- /youtubeService.js: -------------------------------------------------------------------------------- 1 | const { google } = require('googleapis'); 2 | 3 | // Put the following at the top of the file 4 | // right below the'googleapis' import 5 | const util = require('util'); 6 | const fs = require('fs'); 7 | const { response } = require('express'); 8 | const config = require('./config'); 9 | 10 | let liveChatId = config.testLiveChatId; // Where we'll store the id of our liveChat 11 | let nextPage; // How we'll keep track of pagination for chat messages 12 | const minimumChatIntervalInMilliseconds = 8000; // Miliseconds between requests to send chat messages 13 | const maximumChatIntervalInMilliseconds = 300000; // Maximum timeout threshold 14 | const intervals = new Map(); // variable to store and control the interval that will check messages 15 | const updateFunctions = new Map(); 16 | 17 | const writeFilePromise = util.promisify(fs.writeFile); 18 | const readFilePromise = util.promisify(fs.readFile); 19 | 20 | const save = async (path, str) => { 21 | await writeFilePromise(path, str); 22 | console.log('Successfully Saved'); 23 | }; 24 | 25 | const read = async path => { 26 | const fileContents = await readFilePromise(path); 27 | return JSON.parse(fileContents); 28 | }; 29 | 30 | let chatMessages = JSON.parse(fs.readFileSync(config.messageFile), 'utf-8'); 31 | const youtube = google.youtube('v3'); 32 | const OAuth2 = google.auth.OAuth2; 33 | 34 | const clientId = config.clientId; 35 | const clientSecret = config.clientSecret; 36 | const redirectURI = config.redirectURI; 37 | 38 | // Permissions needed to view and submit live chat comments 39 | const scope = [ 40 | 'https://www.googleapis.com/auth/youtube.readonly', 41 | 'https://www.googleapis.com/auth/youtube', 42 | 'https://www.googleapis.com/auth/youtube.force-ssl' 43 | ]; 44 | 45 | const auth = new OAuth2(clientId, clientSecret, redirectURI); 46 | const auths = new Map(); 47 | const youtubeService = {}; 48 | 49 | youtubeService.getCode = response => { 50 | const authUrl = auth.generateAuthUrl({ 51 | access_type: 'offline', 52 | scope 53 | }); 54 | response.redirect(authUrl); 55 | }; 56 | 57 | // Request access from tokens using code from login 58 | youtubeService.getTokensWithCode = async code => { 59 | const credentials = await auth.getToken(code); 60 | youtubeService.authorize(credentials); 61 | }; 62 | 63 | // Storing access tokens received from google in auth object 64 | youtubeService.authorize = async ({ tokens }) => { 65 | console.log(tokens); 66 | let currentAuth = new OAuth2(clientId, clientSecret, redirectURI); 67 | currentAuth.setCredentials(tokens); 68 | const getMyChannelId = await youtube.channels.list({ 69 | auth: currentAuth, 70 | part: 'snippet', 71 | mine: true 72 | }); 73 | const myChannelInfo = getMyChannelId.data.items[0]; 74 | const myChannelId = myChannelInfo.id; 75 | const myChannelName = myChannelInfo.snippet.title; 76 | tokens.name = myChannelName; 77 | auths.set(myChannelId, currentAuth); 78 | console.log('Successfully set credentials'); 79 | console.log('tokens:', tokens); 80 | save('./tokens.json', JSON.stringify([...auths])); 81 | }; 82 | 83 | youtubeService.findActiveChat = async (channelId) => { 84 | var liveId; 85 | const auth = Array.from(auths.values())[0]; 86 | try { 87 | const searchRes = await youtube.search.list({ 88 | auth, 89 | part: 'id', 90 | eventType: 'live', 91 | channelId: channelId, 92 | type: 'video' 93 | }); 94 | const liveIdData = searchRes.data.items; 95 | if (liveIdData.length > 0) { 96 | liveId = liveIdData[0].id.videoId; 97 | console.log("Chat ID Found:", liveId); 98 | const response = await youtube.videos.list({ 99 | auth, 100 | part: 'liveStreamingDetails', 101 | id: liveId 102 | }); 103 | const latestChat = response.data.items[0].liveStreamingDetails.activeLiveChatId; 104 | if (latestChat) { 105 | liveChatId = latestChat; 106 | console.log("Chat ID Found:", liveChatId); 107 | return {found: true, chatId: liveChatId}; 108 | } else { 109 | console.log("No Active Chat Found"); 110 | return {found: false, chatId: null}; 111 | } 112 | } else { 113 | console.log("No Live Stream Found"); 114 | return {found: false, chatId: null}; 115 | } 116 | } catch(err) { 117 | console.log(err); 118 | return {found: false, chatId: null}; 119 | }; 120 | }; 121 | 122 | // Update the tokens automatically when they expire 123 | auth.on('tokens', tokens => { 124 | if (tokens.refresh_token) { 125 | // store the refresh_token in my database! 126 | // save('./tokens.json', JSON.stringify(auth.tokens)); 127 | console.log(tokens.refresh_token); 128 | } 129 | console.log(tokens.access_token); 130 | }); 131 | 132 | // Read tokens from stored file 133 | const checkTokens = async () => { 134 | try { 135 | const tokens = await read('./tokens.json'); 136 | const tokensMap = new Map(tokens); 137 | tokensMap.forEach((value, key) => { 138 | const oAuth = new OAuth2(clientId, clientSecret, redirectURI); 139 | oAuth.setCredentials(value.credentials); 140 | auths.set(key, oAuth); 141 | console.log('token set for ' + value.credentials.name); 142 | }); 143 | } catch(err) { 144 | console.log('no tokens set due to ' + err); 145 | } 146 | }; 147 | 148 | const getChatMessages = async (auth) => { 149 | const response = await youtube.liveChatMessages.list({ 150 | auth, 151 | part: 'snippet,authorDetails', 152 | liveChatId, 153 | pageToken: nextPage 154 | }); 155 | const { data } = response; 156 | const newMessages = data.items; 157 | chatMessages.push(...newMessages.map(message => message.snippet.displayMessage)); 158 | nextPage = data.nextPageToken; 159 | console.log('Total Chat Messages:', chatMessages.length); 160 | }; 161 | 162 | youtubeService.startChatBot = async () => { 163 | auths.forEach((value, key) => { 164 | const myChannelId = key; 165 | const interval = intervals.get(myChannelId); 166 | clearInterval(interval); 167 | }); 168 | console.log("ChatBot Started"); 169 | for (const [key, value] of auths.entries()) { 170 | console.log(value.credentials.name + ": Started"); 171 | const auth = value; 172 | const myChannelId = key; 173 | let updateFunction = updateInterval(minimumChatIntervalInMilliseconds); 174 | updateFunctions.set(myChannelId, updateFunction); 175 | let interval = setTimeout(() => youtubeService.insertMessage(value.credentials.name, myChannelId, auth), minimumChatIntervalInMilliseconds); 176 | intervals.set(myChannelId, interval); 177 | await new Promise(r => setTimeout(r, 3000)); 178 | } 179 | }; 180 | 181 | youtubeService.stopChatBot = () => { 182 | auths.forEach((value, key) => { 183 | const myChannelId = key; 184 | const interval = intervals.get(myChannelId); 185 | clearInterval(interval); 186 | }); 187 | console.log("ChatBot Stopped") 188 | }; 189 | 190 | const updateInterval = (initialChatInterval) => { 191 | var chatInterval = initialChatInterval; 192 | const increase = () => { 193 | chatInterval = chatInterval * 2; 194 | } 195 | const decrease = () => { 196 | if (chatInterval >= minimumChatIntervalInMilliseconds + 1000) 197 | chatInterval = chatInterval - 1000; 198 | } 199 | const update = (lastChatStatus) => { 200 | if (lastChatStatus) { 201 | decrease(); 202 | } else { 203 | increase(); 204 | } 205 | console.log("Current Chat Interval: " + chatInterval); 206 | return chatInterval; 207 | } 208 | return update; 209 | } 210 | 211 | youtubeService.insertMessage = async (name, myChannelId, auth) => { 212 | try { 213 | // await getChatMessages(auth); 214 | var updateInterval = updateFunctions.get(myChannelId); 215 | const response = await youtube.liveChatMessages.insert({ 216 | auth, 217 | part: 'snippet', 218 | resource: { 219 | snippet: { 220 | type: 'textMessageEvent', 221 | liveChatId, 222 | textMessageDetails: { 223 | messageText: chatMessages[Math.floor(Math.random() * chatMessages.length)] 224 | } 225 | } 226 | } 227 | }); 228 | console.log(name + ": Post Chat Succeeded"); 229 | let newInterval = updateInterval(true); 230 | let interval = setTimeout(() => youtubeService.insertMessage(name, myChannelId, auth), newInterval); 231 | intervals.set(myChannelId, interval); 232 | } catch (error) { 233 | console.log(name + ": Post Chat Failed Due To " + error); 234 | var updateInterval = updateFunctions.get(myChannelId); 235 | let newInterval = updateInterval(false); 236 | if (newInterval > maximumChatIntervalInMilliseconds) { 237 | youtubeService.stopChatBot(); 238 | } else { 239 | let interval = setTimeout(() => youtubeService.insertMessage(name, myChannelId, auth), newInterval); 240 | intervals.set(myChannelId, interval); 241 | } 242 | } 243 | }; 244 | 245 | checkTokens(); 246 | 247 | module.exports = youtubeService; 248 | --------------------------------------------------------------------------------