Google Sheet + Apps Script + Glide

 • 

背 GRE 单词的时候弄的一套自动化流程,可以批量查词修改 Spreadsheet,配合 GlideApp 生成 PWA 在手机上用,效果可以参考这里

1) 导入 Apps Script

这个脚本基本上就两个功能,一个是触发器,负责在修改文档的时候更新那一行的内容,第二个是一些数值函数,可以直接在 Sheet 的 f(x) 框里面用。所有查询的词都做了 lowercase 和 cache,毕竟 UrlFetchApp 有配额一天只能跑上几千次,要省着点用,而且如果用了 f(x) 表达式的话,每次打开页面都请求爱词霸也挺拖累别人服务器的。

appsscript.json

{
  "timeZone": "Asia/Hong_Kong",
  "dependencies": {
  },
  "webapp": {
    "access": "ANYONE",
    "executeAs": "USER_ACCESSING"
  },
  "exceptionLogging": "STACKDRIVER",
  "oauthScopes": ["https://www.googleapis.com/auth/documents", "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/spreadsheets"]
}

code.gs

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  // Or DocumentApp or FormApp.
  ui.createMenu('Words')
      .addItem('Reset Cache', 'ResetCache')
      .addToUi();
}

function onEditInstalledTrigger(e) {
  var range = e.range;
  var sheet = SpreadsheetApp.getActiveSheet();
  
  Logger.log("row = " + range.getRow() + " column = " + range.getColumn);
  Logger.log("row Number = " + range.getNumRows() + " column Number = " + range.getNumColumns());
  
  for (var i = range.getRow(); i <= range.getRow() + range.getNumRows() - 1; i++) {
    for (var j = range.getColumn(); j <= range.getColumn() + range.getNumColumns() - 1; j++) 
    {
      if (j == 1)
      {
        var currentValue = sheet.getRange(i,j).getValue();
        var wordLowCase = GetWordLowerCase(currentValue);
        var empty = IsEmpty(wordLowCase);
        var cache = IsCached(wordLowCase);
        
        Logger.log("row i = " + i + " column j = " + i + " word = " + wordLowCase);
        var attribute = {
          cache:cache,
          value:wordLowCase,
          empty:empty
        }
        sheet.getRange(i,j).setNote(empty ? "" : JSON.stringify(attribute));
        sheet.getRange(i,j+1).setValue(empty ? "" : "LOADING");
        sheet.getRange(i,j+2).setValue(empty ? "" : "LOADING");
        sheet.getRange(i,j+3).setValue(empty ? "" : "LOADING");
        sheet.getRange(i,j+4).setValue(empty ? "" : "LOADING");
        sheet.getRange(i,j+5).setValue(empty ? "" : "LOADING");
        
        var wordJsonObject = empty ? {} : GetWord(wordLowCase);
        attribute.json = wordJsonObject;
        sheet.getRange(i,j).setNote(empty ? "" : JSON.stringify(attribute));
        Logger.log(JSON.stringify(attribute));
        
        if (wordJsonObject.status == 0)
        {
          var wordMeaning = empty ? "" : wordJsonObject.content.word_mean.join("\r\n");
          sheet.getRange(i,j+1).setValue(wordMeaning);
          
          var worldPronunciationUS = empty ? "" : "/" + wordJsonObject.content.ph_am + "/";
          sheet.getRange(i,j+2).setValue(worldPronunciationUS);
          
          var worldPronunciationURLUS = empty ? "" : wordJsonObject.content.ph_am_mp3;
          sheet.getRange(i,j+3).setValue(worldPronunciationURLUS);
          
          var cachedString = empty ? "" : (cache ? "YES" : "NO");
          sheet.getRange(i,j+4).setValue(cachedString);
      
        }
        
        var updateTime = empty ? "" : new Date().toISOString();
        sheet.getRange(i,j+5).setValue(updateTime);
       
      }
    }
  }
}

function ResetCache() {
  var cache = CacheService.getScriptCache();
  var selection = SpreadsheetApp.getActiveSheet();
  var data = selection.getSelection().getActiveRange().getValues()
  for (var i = 0; i < data.length; i++) {
    var wordLowCase = GetWordLowerCase(data[i][0]);
    Logger.log('Cache remove: ' + wordLowCase);
    cache.remove(wordLowCase);
  }
}

function GetWordLowerCase(word)
{
  return String(word).toLowerCase();
}

function GetWordUrl(word)
{
  var wordLowCase = GetWordLowerCase(word);
  var url = "http://fy.iciba.com/ajax.php?a=fy&f=auto&t=zh&w=".concat(encodeURIComponent(wordLowCase));
  return url;
}

function IsCached(word)
{
  if (IsEmpty(word))
  {
    return false;
  }
  var wordLowCase = GetWordLowerCase(word);
  var cache = CacheService.getScriptCache();
  var cached = cache.get(wordLowCase);
  return cached != null;
}

function IsEmpty(str) {
    return (!str || 0 === str.length);
}

/**
 * Get Word JSON Object.
 *
 * @param {word} the word.
 * @return JSON returned from iciba.
 * @customfunction
 */
function GetWord(word) {
  if (IsEmpty(word))
  {
    Logger.log("word == null");
    return {};
  }
  var wordLowCase = GetWordLowerCase(word);
  var cache = CacheService.getScriptCache();
  var cached = cache.get(wordLowCase);
  if (cached != null) {
    return JSON.parse(cached);
  }
  var url = GetWordUrl(wordLowCase);
  var jsonData = UrlFetchApp.fetch(url);
  var jsonContent = jsonData.getContentText();
  cache.put(wordLowCase, jsonContent, 604800); // cache for random minutes
  var object   = JSON.parse(jsonContent);
  return object;
}
/**
 * @customfunction
 */
function GetWordMeaning(word)
{
  var object = GetWord(word);
  return object.content.word_mean.join("\r\n");
}
/**
 * @customfunction
 */
function GetWordPronunciationUS(word)
{
  var object = GetWord(word);
  return object.content.ph_am;
}
/**
 * @customfunction
 */
function GetWordPronunciationUSURL(word)
{
  var object = GetWord(word);
  return object.content.ph_am_mp3;
}
/**
 * @customfunction
 */
function GetWordJSON(word)
{
  var object = GetWord(word);
  return JSON.stringify(object);
}

然后部署下,允许下文件访问

2) 安装 Trigger

表格 - 工具 - 脚本触发器 - 修改 - 当前项目触发器 - 添加:

  • 选择要运行的功能 OnEditInstalledTrigger
  • 选择活动来源 基于电子表格
  • 选择活动来源 编辑时
3) 输入数据

第一行最好 freeze 住,然后在第二行开始输入英文,Trigger 会自动补全后面几列
Screenshot-2019-10-24-at-12.44.30-AM

4) 同步到 Glide 里

Glide 里新建一个项目绑定这个表格。
Glide 比较好的地方在于,他们的 Detail Page 可以设定 Audio 组件绑定 URL,刚好可以对应爱词霸 API 里的音标地址,基本配置完可以当一个自定义单词本来用了。

Screenshot-2019-10-24-at-12.49.27-AM-2
Screenshot-2019-10-24-at-12.50.28-AM