Advertisement
TheMexicanRunner

Untitled

Jan 15th, 2016
211
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.12 KB | None | 0 0
  1. /*The MIT License (MIT)
  2.  
  3. Copyright (c) 2015 JP Senior jp.senior@gmail.com
  4.  
  5. Permission is hereby granted, free of charge, to any person obtaining a copy
  6. of this software and associated documentation files (the "Software"), to deal
  7. in the Software without restriction, including without limitation the rights
  8. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the Software is
  10. furnished to do so, subject to the following conditions:
  11.  
  12. The above copyright notice and this permission notice shall be included in
  13. all copies or substantial portions of the Software.
  14.  
  15. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. THE SOFTWARE.
  22. */
  23. /* This application is intended to be used by a Twitch.TV 'nightbot' command
  24. to allow streamers to provide searchign ability for 'games beaten'.
  25. This is intended for completionists.
  26.  
  27. Written by sartan sartandragonbane - with extra help from fo__ for better
  28. code structure. Thanks, Fo!
  29.  
  30. In order to integrate this, you need to create a nightbot command using
  31. the custom API feature.
  32.  
  33. !addcom !search $(customapi https://script.google.com/macros/s/AKfycbzbFd-UHGQVq9biiwgD5GY0GfXpluplrqvPJlG6v88NX4rqtWKP/exec?action=game&query=$(query))
  34. !addcom !invite $(customapi https://script.google.com/macros/s/AKfycbzbFd-UHGQVq9biiwgD5GY0GfXpluplrqvPJlG6v88NX4rqtWKP/exec?action=invite&username=$(touser)&oauth=<OAUTH>)
  35. !addcom !translate $(customapi https://script.google.com/macros/s/AKfycbzbFd-UHGQVq9biiwgD5GY0GfXpluplrqvPJlG6v88NX4rqtWKP/exec?action=translate&from=en&to=pt&text=$(query))
  36. Where the URL included is the twitch URL.
  37.  
  38. */
  39.  
  40. // Change log:
  41. // 2015-11-27 fo: Changed the formatting in formatNewResults() a little bit
  42.  
  43.  
  44. // TODO:
  45. // - Minimum query length
  46. // - Clean up smileys when printing the "not found" message
  47. // - Remove THE/A and such things in clean()
  48. // - Replace roman numerals with numbers (VI -> 6)
  49. // - There's a problem if "&" character (and possibly others) is used in game queries.
  50. // The problem is that the query done by nightbot doesn't escape the & character properly
  51. // (and AFAIK there's no way to make it escape it), so it appears as a start of the next parameter
  52. // to the script. E.g. if query is "battletoads & foobar" nightbot does query: blah?game=battletoads&foobar
  53. // A proper fix would be to get nightbot to escape the parameter, but if that's not possible,
  54. // e.queryString could be used to figure out the full query.
  55. // Total game timer
  56.  
  57.  
  58.  
  59. var ss = SpreadsheetApp.openByUrl(
  60. 'https://docs.google.com/spreadsheets/d/1KDNGI76HoMNyYLL6RqWu4PqUbw-lI920tf7QTclnLLE/edit#gid=0'
  61. );
  62.  
  63. var twitchIcon = 'tmrMames'
  64.  
  65. var inviteRoomName = "Spoilers chat"
  66.  
  67. var inviteRoom = "_fyaaa_1402351320590"
  68.  
  69. // Do not edit anything below this line for end user stuff. //
  70.  
  71. // Little bit less than 400 so that the extra text can fit in.
  72. var MAX_RESULT_LENGTH = 350
  73.  
  74. var integerRegex = /^[0-9]+$/;
  75.  
  76. var asteriskRegex = /^\*.*/;
  77.  
  78.  
  79. function clean( str )
  80. {
  81. return str
  82. .toUpperCase()
  83. .replace( /[^A-Z0-9]/g, "" );
  84. }
  85.  
  86. function formatDate( date )
  87. {
  88. var timezone = ss.getSpreadsheetTimeZone();
  89.  
  90. // If date is a Date, format it. Otherwise use it as is.
  91. if ( date instanceof Date )
  92. return Utilities.formatDate( date, timezone, "M/d/yyyy");
  93. else
  94. return String( date );
  95. }
  96.  
  97. function zeroPad( number )
  98. {
  99. if ( number < 10 )
  100. return "0" + number;
  101.  
  102. return String( number );
  103. }
  104.  
  105. function isEmptyTrimmed( string )
  106. {
  107. return String( string ).trim().length === 0;
  108. }
  109.  
  110. function formatDuration( durationDate )
  111. {
  112. if ( !( durationDate instanceof Date ) )
  113. return String( durationDate );
  114.  
  115. // This is the epoch of "Duration" typed fields in sheet.
  116. // Unfortunately it depends on the time zone that the sheet is set to,
  117. // but should work fine as long as the sheet time zone is not changed.
  118. // It can be found out by fetching a Duration field "00:00" from the sheet.
  119. var epoch = new Date( "Sat, 30 Dec 1899 06:36:36 GMT" );
  120.  
  121. var millis = durationDate - epoch;
  122. var seconds = millis/1000;
  123. var minutes = seconds/60;
  124. var hours = minutes/60;
  125.  
  126. return Math.floor( hours )
  127. + ":" + zeroPad( Math.floor( minutes%60 ) )
  128. + ":" + zeroPad( Math.floor( seconds%60 ) );
  129. }
  130.  
  131. function formatResultLong( result )
  132. {
  133. // Index string may be empty.
  134. var resultString = "";
  135. if ( !isEmptyTrimmed( result.index ) )
  136. resultString += result.index + ". ";
  137.  
  138. // If "date beaten" field is empty, return smth else.
  139. if ( String( result.dateBeaten ).trim().length === 0 )
  140. return resultString + result.game
  141. + " (chosen by " + result.chosenBy
  142. + ") hasn't been beaten yet";
  143.  
  144. resultString += result.game + " (chosen by " + result.chosenBy
  145. + ") was beaten on " + result.dateBeaten;
  146.  
  147. if ( !isEmptyTrimmed( result.time ) )
  148. resultString += " in " + result.time;
  149.  
  150. if ( !isEmptyTrimmed( result.rating ) )
  151. resultString += ", rated " + result.rating + "/10";
  152.  
  153. if ( !isEmptyTrimmed( result.video ) )
  154. resultString += ", Video: " + result.video;
  155.  
  156. return resultString;
  157. }
  158.  
  159. function formatResultShort( result )
  160. {
  161. // Index string may be empty.
  162. var indexString = "";
  163. if ( !isEmptyTrimmed( result.index ) )
  164. indexString += result.index + ". ";
  165.  
  166. return indexString + result.game;
  167. }
  168.  
  169. function checkRow( row, formula, query )
  170. {
  171. var rowIndex = row[0],
  172. rowGame = row[1],
  173. rowChosenBy = row[2],
  174. rowDateBeaten = row[8],
  175. rowTime = row[9],
  176. rowRating = row[5],
  177. rowVideo = formula[14];
  178.  
  179. function makeResult( exact )
  180. {
  181. if ( !isEmptyTrimmed( rowVideo ) ) {
  182. video = rowVideo.match(/\"(.*)\",/)[1]
  183. }
  184. else {
  185. video = ''
  186. }
  187. return {
  188. exact: exact,
  189. index: rowIndex,
  190. game: rowGame,
  191. chosenBy: rowChosenBy,
  192. dateBeaten: formatDate( rowDateBeaten ),
  193. time: formatDuration( rowTime ),
  194. rating: rowRating,
  195. // test Extracts URL from HYPERLINK("link", text)
  196. video: video
  197.  
  198. }
  199. }
  200.  
  201. // Filter out games starting with "*" as they are not real games.
  202. if (asteriskRegex.test ( rowGame )) {
  203. return;
  204. }
  205.  
  206. // If query is an integer, return the game by index.
  207. // The row index must be an integer also (it's empty for e.g. the taco breaks)
  208. // Sartan's note: Row number is not the same as game number. There will always be
  209. // A row index in a sheets document. See above function instead.
  210. // The game name must be non-empty.
  211. if ( integerRegex.test( query ) && integerRegex.test( rowIndex ) )
  212. if ( parseInt( query, 10 ) === parseInt( rowIndex, 10 ) )
  213. if ( !isEmptyTrimmed( rowGame ) )
  214. return makeResult( true );
  215.  
  216. // Otherwise, check if the query is a substring of the game name or
  217. // the nickname.
  218. var queryClean = clean( query );
  219. if ( clean( rowGame ).indexOf( queryClean ) >= 0 ||
  220. clean( rowChosenBy ).indexOf( queryClean ) >= 0 )
  221. {
  222. return makeResult( false );
  223. }
  224.  
  225. // Filter out games starting with "*" as they are not real games.
  226.  
  227. // Didn't match.
  228. return;
  229. }
  230.  
  231. function checkRow_new( row, query )
  232. {
  233. var rowIndex = row[0],
  234. rowGame = row[1],
  235. rowBeaten = row[3],
  236. rowGenre = row[4];
  237. var queryClean = clean( query );
  238. if ( clean( rowGame ).indexOf( queryClean ) >= 0)
  239. {
  240. return {
  241. game: rowGame,
  242. genre: rowGenre
  243. }
  244. }
  245.  
  246. // Didn't match.
  247. return;
  248. }
  249.  
  250. function formatNewResults(results)
  251. {
  252.  
  253. newResults = [];
  254.  
  255. for (var i = 0; i < results.length; ++i)
  256. {
  257. var result = results[i];
  258. newResults.push( result.game + " (" + result.genre + ")" );
  259. }
  260. return newResults
  261.  
  262. }
  263.  
  264.  
  265. function formatLookupResults( results, query )
  266. {
  267. if ( results.length === 0 ) {
  268. //results = lookup( query, "Games List" );
  269. //Logger.log(results);
  270. newresults = lookup( query, "Games List" );
  271. formatted = formatNewResults(newresults)
  272. Logger.log(formatted + formatted.length)
  273. if (formatted.length != 0) {
  274. return twitchIcon + ' ' + formatted + ' was not beaten yet, but can be picked!! ';
  275. }
  276. }
  277. if (results.length === 0 ) {
  278. return twitchIcon + ' Sorry, no entry was found for "' + query + '" ' + twitchIcon;
  279. }
  280.  
  281. // See if there was an exact result.
  282. for ( var i = 0; i < results.length; ++i )
  283. {
  284. var result = results[i];
  285.  
  286. // If an exact result (from integer query), return only it.
  287. if ( result.exact )
  288. return formatResultLong( result );
  289. }
  290.  
  291. // No exact result, build the string from multiple results.
  292.  
  293. // First result is always long.
  294. var resultString = formatResultLong( results[0] );
  295.  
  296. // If there are more results, display only the number and name.
  297. if ( results.length > 1 )
  298. {
  299. var otherStrings = [];
  300. for ( var i = 1; i < results.length; ++i )
  301. otherStrings.push( formatResultShort( results[i] ) );
  302.  
  303. resultsNumber = "result";
  304. if ( otherStrings.length > 1 )
  305. resultsNumber += "s";
  306. resultString += " [" + otherStrings.length + " other " + resultsNumber + ": ";
  307.  
  308. // Add results until we reach the maximum string length.
  309. for ( var i = 0; i < otherStrings.length; ++i )
  310. {
  311. var string = otherStrings[i];
  312. // If the new string would push us over the maximum length...
  313. if ( resultString.length + string.length > MAX_RESULT_LENGTH )
  314. {
  315. var numRest = otherStrings.length - i;
  316. resultString += "(and " + numRest + " more)";
  317.  
  318. // We're done.
  319. break;
  320. }
  321.  
  322. resultString += string;
  323. if ( i != otherStrings.length-1 )
  324. resultString += " - ";
  325. }
  326.  
  327. resultString += "]";
  328. }
  329.  
  330. return resultString;
  331. }
  332.  
  333.  
  334. // Function that looks up the Genres breakdown
  335. // based on 'query'
  336. // if query isn't in the spreadsheet, just return the last 'total' row result.
  337.  
  338. function genre(query)
  339. {
  340. var sheet = ss.getSheetByName( "Genres Breakdown" );
  341. var data = sheet.getDataRange().getValues();
  342. var result = '';
  343. for ( var i = 1; i < sheet.getLastRow(); i++ )
  344. {
  345. var types = data[i][0];
  346. var total = data[i][1];
  347. var beaten = data[i][2];
  348. var left = data[i][3];
  349. var ratio = data[i][4];
  350. var time = data[i][5];
  351. var average = data[i][6];
  352. var shortesttime = data[i][7];
  353. var shortestname = data[i][8];
  354. var longesttime = data[i][9];
  355. var longestname = data[i][10];
  356.  
  357. var test = ratio.valueOf(test);
  358. var percent = Math.round(ratio*10000)/100
  359.  
  360. if ( clean ( types ) == 'TOTAL') {
  361. var totalresult = 'tmrHat has beaten ' + beaten + '/' + total + ' ' + types + ' games - ' + percent + '% of NESMania';
  362. }
  363. else if ( clean ( types ) == clean (query) ) {
  364. return 'tmrHat has beaten ' + beaten + '/' + total + ' ' + types + ' games - ' + percent + '%';
  365. }
  366. }
  367. return totalresult;
  368. }
  369.  
  370. function lookup( query, sheetname )
  371. {
  372. var sheet = ss.getSheetByName( sheetname );
  373. var data = sheet.getDataRange().getValues();
  374. var formula = sheet.getDataRange().getFormulas();
  375. var allResults = []
  376.  
  377. // Sartan: Only send a search if there are more than 1 characters to query.
  378. if ( clean( query ).length >= 1 )
  379. {
  380. // First row is the header, so skip it.
  381. for ( var i = 1; i < data.length; ++i )
  382. {
  383. if ( sheetname == "Games Beaten" ) {
  384. var rowResult = checkRow( data[i], formula[i], query );
  385. }
  386. else if ( sheetname == "Games List") {
  387. var rowResult = checkRow_new( data[i], query );
  388. }
  389.  
  390. if ( rowResult !== undefined )
  391. allResults.push( rowResult );
  392. }
  393. }
  394.  
  395. return allResults;
  396. }
  397.  
  398. function doGet( e )
  399. {
  400. // This test query is used if nothing is passed in "e".
  401. var TEST_QUERY = "sports";
  402. // invite or game
  403. //var TEST_MODE = 'translate';
  404. var TEST_MODE = 'genre';
  405.  
  406. var result = "n/a";
  407.  
  408. // Query must be present to run commands
  409. if ( e !== undefined ) {
  410. Logger.log( "Full query: " + e.queryString );
  411.  
  412. result = "Invalid action";
  413.  
  414. if ( e.parameter.action !== undefined )
  415. {
  416. if ( e.parameter.action === "game" ) {
  417. var queryPrefix = 'action=game&query=';
  418.  
  419. // Sanity check:
  420. if ( e.queryString.indexOf( queryPrefix ) === 0 )
  421. {
  422. // NOTE: This is a hack. Everything after "query=" is considered
  423. // part of the game query. It's needed because of nightbot's limitations.
  424. var query = e.queryString.replace( queryPrefix, '' );
  425. query = decodeURI(query);
  426. // if lookup(query) is empty here, check the other sheet if a game is still waiting to be patched
  427.  
  428. result = formatLookupResults( lookup( query, "Games Beaten" ), query );
  429. }
  430. else
  431. {
  432. result = "Sanity check failed. Hell has frozen over.";
  433. }
  434. }
  435. else if ( e.parameter.action === "invite" ) {
  436. var username = e.parameter.username;
  437. var oauth = e.parameter.oauth;
  438. result = sendInvite( username, oauth );
  439. }
  440. else if ( e.parameter.action === "genre" ) {
  441. var query = clean( e.parameter.query );
  442. result = genre( query );
  443. }
  444. else if ( e.parameter.action === "translate" ) {
  445. var text = e.parameter.text;
  446. var from = e.parameter.from;
  447. var to = e.parameter.to;
  448. result = gtranslate( text, from, to );
  449. }
  450. }
  451. }
  452. // Sartan: Test Mode
  453. else {
  454. if ( TEST_MODE == 'game' ) {
  455. query = TEST_QUERY;
  456. result = formatLookupResults( lookup( query, "Games Beaten" ), query );
  457. }
  458. else if ( TEST_MODE == 'invite') {
  459. result = sendInvite( 'bot_sartan', 'invalid_oauth' );
  460. }
  461. else if ( TEST_MODE == 'translate') {
  462. result = gtranslate( 'one two three', "en", "pt" );
  463. }
  464. else if ( TEST_MODE == 'genre') {
  465. result = genre(TEST_QUERY);
  466. }
  467. }
  468.  
  469. Logger.log( "Lookup results: " + result );
  470. return ContentService.createTextOutput( result );
  471. }
  472.  
  473.  
  474. function sendInvite(username, oauth) {
  475.  
  476. var payload =
  477. {
  478. "irc_channel" : inviteRoom,
  479. "username" : username
  480. };
  481.  
  482. var options =
  483. {
  484. "oauth_token" : oauth,
  485. "method" : "post",
  486. "payload" : payload,
  487. // We want the HTTP code if the request fails.
  488. "muteHttpExceptions" : true
  489. };
  490.  
  491. var response = UrlFetchApp.fetch("https://chatdepot.twitch.tv/room_memberships?oauth_token=" + oauth, options);
  492. var err = JSON.parse(response)
  493. Logger.log(response)
  494. Logger.log(err)
  495.  
  496. var code = response.getResponseCode()
  497. if (code == 401) {
  498. return "Authorization failure to Twitch Chat Depot, Check script and nightbot oauth: Notify SartanDragonbane - " + err.message
  499. }
  500. else if ( code == 200 ) {
  501. return "Welcome to " + inviteRoomName + " " + username + ", Please refresh Twitch Chat to see the new group above the chat window."
  502. }
  503. else {
  504. return "Invite failed. Contact SartanDragonBane or fo__ if this is in error. HTTP Response code: " + code + " Message: " + err.message + " Error: " + err.errors
  505. }
  506. }
  507.  
  508. // Language codes available here: https://cloud.google.com/translate/v2/using_rest#language-params
  509. function gtranslate(text, from, to) {
  510.  
  511. return from + ">" + to + ": " + LanguageApp.translate( text, from, to );
  512.  
  513. }
  514.  
  515. // Average Rate of Strawpoll made by Zappelin
  516.  
  517. function getavg(arr) {
  518. var total=0, numvotes=0;
  519. for (i = 1; i <= arr[0].length ; i++) {
  520. total += arr[0][i-1] * i;
  521. numvotes += arr[0][i-1];
  522. }
  523. if( numvotes == 0 ) { return 0; };
  524. var avg = total / numvotes;
  525. return avg.toFixed(1);
  526. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement