// Github: https://github.com/shdwjk/Roll20API/blob/master/TableExport/TableExport.js // By: The Aaron, Arcane Scriptomancer // Contact: https://app.roll20.net/users/104025/the-aaron var TableExport = TableExport || (function() { 'use strict'; var version = '0.2.3', lastUpdate = 1453209012, tableCache = {}, escapes = { '[' : '<%%91%%>', ']' : '<%%93%%>', '--' : '<%%-%%>', ' --' : '[TABLEEXPORT:ESCAPE]' }, esRE = function (s) { var escapeForRegexp = /(\\|\/|\[|\]|\(|\)|\{|\}|\?|\+|\*|\||\.|\^|\$)/g; return s.replace(escapeForRegexp,"\\$1"); }, ch = function (c) { var entities = { '<' : 'lt', '>' : 'gt', "'" : '#39', '@' : '#64', '{' : '#123', '|' : '#124', '}' : '#125', '[' : '#91', ']' : '#93', '"' : 'quot', '-' : 'mdash', ' ' : 'nbsp' }; if(_.has(entities,c) ){ return ('&'+entities[c]+';'); } return ''; }, checkInstall = function() { log('-=> TableExport v'+version+' <=- ['+(new Date(lastUpdate*1000))+']'); }, showHelp = function() { sendChat('', '/w gm ' +'
' +'
' +'TableExport v'+version +'
' +'
' +'

This script dumps commands to the chat for reconstructing a rollable table on another campaign. While this can be done on your own campaigns via the transmogrifyer, this script allows you to pass those commands to a friend and thus share your own creative works with others.

' +'

Caveat: Avatar images that are not in your own library will be ignored by the API on import, but will not prevent creation of the table and table items.

' +'
' +'Commands' +'
' +'!export-table --'+ch('<')+'Table Name'+ch('>')+' [ --'+ch('<')+'Table Name'+ch('>')+' ...]' +'
' +'

For all table names, case is ignored and you may use partial names so long as they are unique. For example, '+ch('"')+'King Maximillian'+ch('"')+' could be called '+ch('"')+'max'+ch('"')+' as long as '+ch('"')+'max'+ch('"')+' does not appear in any other table names. Exception: An exact match will trump a partial match. In the previous example, if a table named '+ch('"')+'Max'+ch('"')+' existed, it would be the only table matched for --max.

' +'
    ' +'
  • ' +'--'+ch('<')+'Table Name'+ch('>')+' '+ch('-')+' This is the name of a table to export. You can specify as many tables as you like in a single command.' +'
  • ' +'
' +'
' +'
' +'
' +'!import-table --'+ch('<')+'Table Name'+ch('>')+' --'+ch('<')+'[ show | hide ]'+ch('>')+'' +'
' +'

This is the command output by !export-table to create the new table. You likely will not need issue these commands directly.

' +'
    ' +'
  • ' +'--'+ch('<')+'Table Name'+ch('>')+' '+ch('-')+' This is the name of the table to be create.' +'
  • ' +'
  • ' +'--'+ch('<')+'[ show | hide ]'+ch('>')+' '+ch('-')+' This whether to show the table to players or hide it.' +'
  • ' +'
' +'
' +'
' +'
' +'!import-table-item --'+ch('<')+'Table Name'+ch('>')+' --'+ch('<')+'Table Item Name'+ch('>')+' --'+ch('<')+'Weight'+ch('>')+' --'+ch('<')+'Avatar URL'+ch('>')+'' +'
' +'

This is the command output by !export-table to create the new table. You likely will not need issue these commands directly.

' +'
    ' +'
  • ' +'--'+ch('<')+'Table Name'+ch('>')+' '+ch('-')+' This is the name of the table to add items to. Note: unlike for !export-table, this must be an exact name match to the created table.' +'
  • ' +'
  • ' +'--'+ch('<')+'Table Item Name'+ch('>')+' '+ch('-')+' This is the name of the table item to create. Note: Because the string '+ch('"')+' --'+ch('"')+' may occur in a table item name, you may see '+ch('"')+'[TABLEEXPORT:ESCAPE]'+ch('"')+' show up as a replacement in these commands. This value is corrected internally to the correct '+ch('"')+' --'+ch('"')+' sequence on import.' +'
  • ' +'
  • ' +'--'+ch('<')+'Weight'+ch('>')+' '+ch('-')+' This is the weight for this table item and should be an integer value.' +'
  • ' +'
  • ' +'--'+ch('<')+'Avatar URL'+ch('>')+' '+ch('-')+' This is the URL for the avatar image of the table item.' +'
  • ' +'
' +'
' +'
' +'
' ); }, nameEscape = (function(){ var re=new RegExp('('+_.map(_.keys(escapes),esRE).join('|')+')','g'); return function(s){ return s.replace(re, function(c){ return escapes[c] || c; }); }; }()), nameUnescape = (function(){ var sepacse = _.invert(escapes), re=new RegExp('('+_.map(_.keys(sepacse),esRE).join('|')+')','g'); return function(s){ return s.replace(re, function(c){ return sepacse[c] || c; }); }; }()), handleInput = function(msg) { var args, matches, tables, tableIDs=[], errors=[], items, itemMatches, accum=''; if (msg.type !== "api" || !playerIsGM(msg.playerid)) { return; } args = msg.content.split(/\s+/); switch(args[0]) { case '!import-table': args = msg.content.split(/\s+--/); if(args.length === 1) { showHelp(); break; } if(_.has(tableCache,args[1])) { sendChat('','/w gm ' +'
' +'Warning: ' +'Table ['+args[1]+'] already exists, skipping create.' +'
' ); } else { tableIDs=findObjs({type: 'rollabletable', name: args[1]}); if(tableIDs.length) { sendChat('','/w gm ' +'
' +'Warning: ' +'Table ['+args[1]+'] already exists, skipping create.' +'
' ); } else { tableIDs=createObj('rollabletable',{ name: args[1], showplayers: ('show'===args[2]) }); tableCache[args[1]]=tableIDs.id; } } break; case '!import-table-item': args = msg.content.split(/\s+--/); if(args.length === 1) { showHelp(); break; } args[2] = nameUnescape(args[2]); if(!_.has(tableCache,args[1])) { tableIDs=findObjs({type: 'rollabletable', name: args[1]}); if(!tableIDs.length) { sendChat('','/w gm ' +'
' +'Error: ' +'Table ['+args[1]+'] doesn not exist. Cannot create table item.' +'
' ); break; } else { tableCache[args[1]]=tableIDs[0].id; } } createObj('tableitem',{ name: args[2], rollabletableid: tableCache[args[1]], weight: parseInt(args[3],10)||1, avatar: args[4]||'' }); break; case '!export-table': args = msg.content.split(/\s+--/); if(args.length === 1) { showHelp(); break; } tables=findObjs({type: 'rollabletable'}); matches=_.chain(args) .rest() .map(function(n){ var l=_.filter(tables,function(t){ return t.get('name').toLowerCase() === n.toLowerCase(); }); return ( 1 === l.length ? l : _.filter(tables,function(t){ return -1 !== t.get('name').toLowerCase().indexOf(n.toLowerCase()); })); }) .value(); _.each(matches,function(o,idx){ if(1 !== o.length) { if(o.length) { errors.push('Rollable Table ['+args[idx+1]+'] is ambiguous and matches '+o.length+' names: '+_.map(o,function(e){ return e.get('name'); }).join(', ')+''); } else { errors.push('Rollable Table ['+args[idx+1]+'] does not match any names.'); } } },errors); if(errors.length) { sendChat('','/w gm ' +'
' +'
Error: ' +errors.join('
Error: ') +'
' +'
' ); break; } if( ! errors.length) { matches=_.chain(matches) .flatten(true) .map(function(t){ tableIDs.push(t.id); return t; }) .value(); items=findObjs({type: 'tableitem'}); itemMatches=_.chain(items) .filter(function(i){ return _.contains(tableIDs,i.get('rollabletableid')); }) .reduce(function(memo,e){ if(!_.has(memo,e.get('rollabletableid'))) { memo[e.get('rollabletableid')]=[e]; } else { memo[e.get('rollabletableid')].push(e); } return memo; },{}) .value(); _.each(matches, function(t){ accum+='!import-table --'+nameEscape(t.get('name'))+' --'+(t.get('showplayers') ? 'show' : 'hide')+'
'; _.each(itemMatches[t.id], function(i){ accum+='!import-table-item --'+nameEscape(t.get('name'))+' --'+nameEscape(i.get('name'))+' --'+i.get('weight')+' --'+i.get('avatar')+'
'; }); }); sendChat('', '/w gm '+accum); } break; } }, handleRemoveTable = function(obj){ tableCache = _.without(tableCache,obj.id); }, registerEventHandlers = function() { on('chat:message', handleInput); on('destroy:rollabletable', handleRemoveTable); }; return { CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers }; }()); on("ready",function(){ 'use strict'; TableExport.CheckInstall(); TableExport.RegisterEventHandlers(); });