├── .gitignore ├── Android ├── ClientCode │ ├── AndroidManifest.xml │ ├── README │ ├── gen │ │ └── com │ │ │ └── google │ │ │ └── android │ │ │ └── gcm │ │ │ └── demo │ │ │ └── app │ │ │ ├── BuildConfig.java │ │ │ ├── Manifest.java │ │ │ └── R.java │ ├── libs │ │ ├── android-support-v4.jar │ │ └── gcm.jar │ ├── project.properties │ ├── res │ │ ├── drawable-hdpi-v11 │ │ │ └── ic_stat_gcm.png │ │ ├── drawable-hdpi-v9 │ │ │ └── ic_stat_gcm.png │ │ ├── drawable-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_stat_gcm.png │ │ ├── drawable-ldpi-v11 │ │ │ └── ic_stat_gcm.png │ │ ├── drawable-ldpi-v9 │ │ │ └── ic_stat_gcm.png │ │ ├── drawable-ldpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_stat_gcm.png │ │ ├── drawable-mdpi-v11 │ │ │ └── ic_stat_gcm.png │ │ ├── drawable-mdpi-v9 │ │ │ └── ic_stat_gcm.png │ │ ├── drawable-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_stat_gcm.png │ │ ├── drawable-xhdpi-v11 │ │ │ └── ic_stat_gcm.png │ │ ├── drawable-xhdpi-v9 │ │ │ └── ic_stat_gcm.png │ │ ├── drawable-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_stat_gcm.png │ │ ├── layout │ │ │ ├── main.xml │ │ │ └── rowlayout.xml │ │ ├── menu │ │ │ ├── inventory.xml │ │ │ └── options_menu.xml │ │ ├── values-sw600dp │ │ │ └── dimens.xml │ │ ├── values-sw720dp-land │ │ │ └── dimens.xml │ │ └── values │ │ │ ├── dimens.xml │ │ │ └── strings.xml │ └── src │ │ └── com │ │ └── google │ │ └── android │ │ └── gcm │ │ └── demo │ │ └── app │ │ ├── CommonUtilities.java │ │ ├── DemoActivity.java │ │ ├── GCMIntentService.java │ │ ├── Item.java │ │ ├── ServerUtilities.java │ │ └── SimpleArrayAdapter.java ├── GCM_Registrar_Script │ ├── code.gs │ ├── scriptdb.gs │ └── ui.html ├── InventoryContentService.gs ├── SharedDb.gs ├── readme.md └── track-and-notify.gs ├── ContentService ├── Addressbook-VCF │ ├── Code.gs │ └── vcard_template.html ├── Gmail-RSS │ ├── code.gs │ └── rss.html ├── Inventory-JSON-API-iOS │ ├── Inventory-iOS │ │ ├── Default-568h@2x.png │ │ ├── Inventory.xcodeproj │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcuserdata │ │ │ │ │ └── anagarajan.xcuserdatad │ │ │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcuserdata │ │ │ │ └── anagarajan.xcuserdatad │ │ │ │ ├── xcdebugger │ │ │ │ └── Breakpoints.xcbkptlist │ │ │ │ └── xcschemes │ │ │ │ ├── InventorySample.xcscheme │ │ │ │ └── xcschememanagement.plist │ │ └── Inventory │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Inventory-Info.plist │ │ │ ├── Inventory-Prefix.pch │ │ │ ├── MasterViewController.h │ │ │ ├── MasterViewController.m │ │ │ ├── en.lproj │ │ │ ├── InfoPlist.strings │ │ │ └── MainStoryboard.storyboard │ │ │ └── main.m │ └── code.gs ├── Simple-JSONP │ ├── SimpleHtml-JSFiddle.html │ └── code.gs └── readme.md ├── DriveSDK ├── AppsScript.gs ├── ChromeWebStoreVersion │ └── SimpleZipAndSend.gs ├── FlowManager.gs └── readme.md ├── FusionTables ├── RowUtilities.gs ├── SampleDataExchange.gs └── readme.md ├── GoogleAppsAdminAudit.gs ├── IO2013 ├── Drive │ ├── Code.gs │ ├── README.md │ ├── SpreadsheetUtils.gs │ └── ui.html └── YouTubeAnalytics │ ├── Code.gs │ ├── README.md │ ├── oauth2.gs │ ├── oauthsuccess.html │ ├── twitter.gs │ └── ui.html ├── NYTimesCampaignContribution.gs ├── README.md ├── SAP ├── Forms │ ├── Code.gs │ └── product_template.html ├── Gmail Schemas │ ├── Code.gs │ └── mail_template.html ├── README.md └── Sheets │ ├── Code.gs │ └── ui.html ├── Salesforce.com ├── OAuthAndUploadContactsToSalesforce.gs ├── RowUtilities.gs ├── ScanEmailToSalesforce.gs └── readme.md ├── ScriptDbVisualizer ├── Code.gs ├── ScriptDbConsole.html └── readme.md ├── Twilio ├── MakePhoneCall │ ├── .DS_Store │ ├── MakePhoneCall.gs │ └── twiml.html ├── RecieveSMS │ └── RecieveSMS.gs └── readme.md ├── WhiteHouseHackday ├── Code.gs ├── README.md └── ui.html └── YouTube ├── CreateGDLCalendar.gs ├── YouTubeAnalytics.gs └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | #ignore these files 2 | .DS_Store -------------------------------------------------------------------------------- /Android/ClientCode/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 23 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 59 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 77 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Android/ClientCode/README: -------------------------------------------------------------------------------- 1 | GCM demo app 2 | ------------ 3 | 4 | 1.To create the project settings, type: 5 | 6 | android update project --name GCMDemo -p . --target android-16 7 | 8 | 2.To build the app, type: 9 | 10 | ant clean debug 11 | 12 | (but set SENDER_ID and SERVER_URL on /src/com/google/android/gcm/demo/app/CommonUtilities.java first) 13 | 14 | 3.To install the app, type 15 | 16 | ant installd 17 | -------------------------------------------------------------------------------- /Android/ClientCode/gen/com/google/android/gcm/demo/app/BuildConfig.java: -------------------------------------------------------------------------------- 1 | /** Automatically generated file. DO NOT MODIFY */ 2 | package com.google.android.gcm.demo.app; 3 | 4 | public final class BuildConfig { 5 | public final static boolean DEBUG = true; 6 | } -------------------------------------------------------------------------------- /Android/ClientCode/gen/com/google/android/gcm/demo/app/Manifest.java: -------------------------------------------------------------------------------- 1 | /* AUTO-GENERATED FILE. DO NOT MODIFY. 2 | * 3 | * This class was automatically generated by the 4 | * aapt tool from the resource data it found. It 5 | * should not be modified by hand. 6 | */ 7 | 8 | package com.google.android.gcm.demo.app; 9 | 10 | public final class Manifest { 11 | public static final class permission { 12 | /** 13 | Creates a custom permission so only this app can receive its messages. 14 | 15 | NOTE: the permission *must* be called PACKAGE.permission.C2D_MESSAGE, 16 | where PACKAGE is the application's package name. 17 | 18 | */ 19 | public static final String C2D_MESSAGE="com.google.android.gcm.demo.app.permission.C2D_MESSAGE"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Android/ClientCode/gen/com/google/android/gcm/demo/app/R.java: -------------------------------------------------------------------------------- 1 | /* AUTO-GENERATED FILE. DO NOT MODIFY. 2 | * 3 | * This class was automatically generated by the 4 | * aapt tool from the resource data it found. It 5 | * should not be modified by hand. 6 | */ 7 | 8 | package com.google.android.gcm.demo.app; 9 | 10 | public final class R { 11 | public static final class attr { 12 | } 13 | public static final class dimen { 14 | /** Default screen margins, per the Android Design guidelines. 15 | 16 | Customize dimensions originally defined in res/values/dimens.xml (such as 17 | screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here. 18 | 19 | */ 20 | public static final int activity_horizontal_margin=0x7f040000; 21 | public static final int activity_vertical_margin=0x7f040001; 22 | } 23 | public static final class drawable { 24 | public static final int ic_launcher=0x7f020000; 25 | public static final int ic_stat_gcm=0x7f020001; 26 | } 27 | public static final class id { 28 | public static final int action_settings=0x7f070004; 29 | public static final int display=0x7f070000; 30 | public static final int inventoryCount=0x7f070003; 31 | public static final int inventoryName=0x7f070001; 32 | public static final int label=0x7f070002; 33 | public static final int options_clear=0x7f070007; 34 | public static final int options_exit=0x7f070008; 35 | public static final int options_refresh_data=0x7f070009; 36 | public static final int options_register=0x7f070005; 37 | public static final int options_unregister=0x7f070006; 38 | } 39 | public static final class layout { 40 | public static final int main=0x7f030000; 41 | public static final int rowlayout=0x7f030001; 42 | } 43 | public static final class menu { 44 | public static final int inventory=0x7f060000; 45 | public static final int options_menu=0x7f060001; 46 | } 47 | public static final class string { 48 | public static final int action_settings=0x7f050013; 49 | public static final int already_registered=0x7f050002; 50 | public static final int app_name=0x7f050000; 51 | public static final int error_config=0x7f050001; 52 | public static final int gcm_deleted=0x7f050008; 53 | public static final int gcm_error=0x7f050006; 54 | public static final int gcm_message=0x7f050005; 55 | public static final int gcm_recoverable_error=0x7f050007; 56 | public static final int gcm_registered=0x7f050003; 57 | public static final int gcm_unregistered=0x7f050004; 58 | public static final int hello_world=0x7f050014; 59 | public static final int options_clear=0x7f050010; 60 | public static final int options_exit=0x7f050011; 61 | public static final int options_refresh_data=0x7f050015; 62 | public static final int options_register=0x7f05000e; 63 | public static final int options_unregister=0x7f05000f; 64 | public static final int server_register_error=0x7f05000c; 65 | public static final int server_registered=0x7f05000a; 66 | public static final int server_registering=0x7f050009; 67 | public static final int server_unregister_error=0x7f05000d; 68 | public static final int server_unregistered=0x7f05000b; 69 | public static final int title_activity_inventory=0x7f050012; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Android/ClientCode/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/libs/android-support-v4.jar -------------------------------------------------------------------------------- /Android/ClientCode/libs/gcm.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/libs/gcm.jar -------------------------------------------------------------------------------- /Android/ClientCode/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-hdpi-v11/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-hdpi-v11/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-hdpi-v9/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-hdpi-v9/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-hdpi/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-hdpi/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-ldpi-v11/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-ldpi-v11/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-ldpi-v9/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-ldpi-v9/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-ldpi/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-ldpi/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-mdpi-v11/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-mdpi-v11/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-mdpi-v9/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-mdpi-v9/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-mdpi/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-mdpi/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-xhdpi-v11/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-xhdpi-v11/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-xhdpi-v9/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-xhdpi-v9/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android/ClientCode/res/drawable-xhdpi/ic_stat_gcm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Android/ClientCode/res/drawable-xhdpi/ic_stat_gcm.png -------------------------------------------------------------------------------- /Android/ClientCode/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 27 | 31 | -------------------------------------------------------------------------------- /Android/ClientCode/res/layout/rowlayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 14 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Android/ClientCode/res/menu/inventory.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /Android/ClientCode/res/menu/options_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 24 | 34 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Android/ClientCode/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /Android/ClientCode/res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 128dp 8 | 9 | -------------------------------------------------------------------------------- /Android/ClientCode/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | -------------------------------------------------------------------------------- /Android/ClientCode/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | Apps Script GDL 20 | Please set the %1$s constant and recompile the app. 21 | Device is already registered on server. 22 | From GCM: device successfully registered! 23 | From GCM: device successfully unregistered! 24 | From GCM: you got message! 25 | From GCM: error (%1$s). 26 | From GCM: recoverable error (%1$s). 27 | From GCM: server deleted %1$d pending messages! 28 | Trying (attempt %1$d/%2$d) to register device on Demo Server. 29 | From Demo Server: successfully added device! 30 | From Demo Server: successfully removed device! 31 | Could not register device on Demo Server after %1$d attempts. 32 | Could not unregister device on Demo Server (%1$s). 33 | Register 34 | Unregister 35 | Clear 36 | Exit 37 | Inventory 38 | Settings 39 | Hello world! 40 | Refresh Data 41 | 42 | -------------------------------------------------------------------------------- /Android/ClientCode/src/com/google/android/gcm/demo/app/CommonUtilities.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.gcm.demo.app; 17 | 18 | import android.content.Context; 19 | import android.content.Intent; 20 | 21 | /** 22 | * Helper class providing methods and constants common to other classes in the 23 | * app. 24 | */ 25 | public final class CommonUtilities { 26 | 27 | /** 28 | * Base URL of the Demo Server (such as http://my_host:8080/gcm-demo) 29 | */ 30 | static final String SERVER_URL = "https://script.google.com/macros/s/YOUR_SERVER_URL/exec"; 31 | 32 | /** 33 | * Google API project id registered to use GCM. 34 | */ 35 | static final String SENDER_ID = "YOUR_SENDER_ID"; 36 | 37 | /** 38 | * Tag used on log messages. 39 | */ 40 | static final String TAG = "GCMDemo"; 41 | 42 | /** 43 | * Intent used to display a message in the screen. 44 | */ 45 | static final String DISPLAY_MESSAGE_ACTION = 46 | "com.google.android.gcm.demo.app.DISPLAY_MESSAGE"; 47 | 48 | /** 49 | * Intent's extra that contains the message to be displayed. 50 | */ 51 | static final String EXTRA_MESSAGE = "message"; 52 | 53 | /** 54 | * Notifies UI to display a message. 55 | *

56 | * This method is defined in the common helper because it's used both by 57 | * the UI and the background service. 58 | * 59 | * @param context application's context. 60 | * @param message message to be displayed. 61 | */ 62 | static void displayMessage(Context context, String message) { 63 | Intent intent = new Intent(DISPLAY_MESSAGE_ACTION); 64 | intent.putExtra(EXTRA_MESSAGE, message); 65 | context.sendBroadcast(intent); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Android/ClientCode/src/com/google/android/gcm/demo/app/DemoActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.gcm.demo.app; 17 | 18 | import static com.google.android.gcm.demo.app.CommonUtilities.DISPLAY_MESSAGE_ACTION; 19 | import static com.google.android.gcm.demo.app.CommonUtilities.EXTRA_MESSAGE; 20 | import static com.google.android.gcm.demo.app.CommonUtilities.SENDER_ID; 21 | import static com.google.android.gcm.demo.app.CommonUtilities.SERVER_URL; 22 | 23 | import java.util.ArrayList; 24 | 25 | import org.apache.http.HttpResponse; 26 | import org.apache.http.HttpStatus; 27 | import org.apache.http.client.HttpClient; 28 | import org.apache.http.client.methods.HttpGet; 29 | import org.apache.http.impl.client.DefaultHttpClient; 30 | import org.apache.http.util.EntityUtils; 31 | import org.json.JSONArray; 32 | import org.json.JSONObject; 33 | 34 | import com.google.android.gcm.GCMRegistrar; 35 | 36 | import android.app.ListActivity; 37 | import android.content.BroadcastReceiver; 38 | import android.content.Context; 39 | import android.content.Intent; 40 | import android.content.IntentFilter; 41 | import android.os.AsyncTask; 42 | import android.os.Bundle; 43 | import android.util.Log; 44 | import android.view.Menu; 45 | import android.view.MenuInflater; 46 | import android.view.MenuItem; 47 | import android.widget.TextView; 48 | 49 | /** 50 | * Main UI for the demo app. 51 | */ 52 | public class DemoActivity extends ListActivity { 53 | 54 | TextView mDisplay; 55 | AsyncTask mRegisterTask; 56 | 57 | @Override 58 | public void onCreate(Bundle savedInstanceState) { 59 | super.onCreate(savedInstanceState); 60 | checkNotNull(SERVER_URL, "SERVER_URL"); 61 | checkNotNull(SENDER_ID, "SENDER_ID"); 62 | // Make sure the device has the proper dependencies. 63 | GCMRegistrar.checkDevice(this); 64 | // Make sure the manifest was properly set - comment out this line 65 | // while developing the app, then uncomment it when it's ready. 66 | GCMRegistrar.checkManifest(this); 67 | setContentView(R.layout.main); 68 | mDisplay = (TextView) findViewById(R.id.display); 69 | registerReceiver(mHandleMessageReceiver, 70 | new IntentFilter(DISPLAY_MESSAGE_ACTION)); 71 | final String regId = GCMRegistrar.getRegistrationId(this); 72 | final Context context = this; 73 | 74 | if (regId.equals("")) { 75 | // Automatically registers application on startup. 76 | GCMRegistrar.register(this, SENDER_ID); 77 | } else { 78 | // Device is already registered on GCM, check server. 79 | if (GCMRegistrar.isRegisteredOnServer(this)) { 80 | // Skips registration. 81 | mDisplay.append(getString(R.string.already_registered) + "\n"); 82 | } else { 83 | // Try to register again, but not in the UI thread. 84 | // It's also necessary to cancel the thread onDestroy(), 85 | // hence the use of AsyncTask instead of a raw thread. 86 | mRegisterTask = new AsyncTask() { 87 | 88 | @Override 89 | protected Void doInBackground(Void... params) { 90 | boolean registered = 91 | ServerUtilities.register(context, regId); 92 | // At this point all attempts to register with the app 93 | // server failed, so we need to unregister the device 94 | // from GCM - the app will try to register again when 95 | // it is restarted. Note that GCM will send an 96 | // unregistered callback upon completion, but 97 | // GCMIntentService.onUnregistered() will ignore it. 98 | if (!registered) { 99 | GCMRegistrar.unregister(context); 100 | } 101 | return null; 102 | } 103 | 104 | @Override 105 | protected void onPostExecute(Void result) { 106 | mRegisterTask = null; 107 | } 108 | 109 | }; 110 | mRegisterTask.execute(null, null, null); 111 | } 112 | } 113 | 114 | MyTask task = new MyTask(); 115 | task.execute(); 116 | 117 | } 118 | 119 | private class MyTask extends AsyncTask { 120 | ArrayList items = new ArrayList(); 121 | @Override 122 | protected Void doInBackground(Void... params) { 123 | try { 124 | HttpClient hc = new DefaultHttpClient(); 125 | String URL = "https://script.google.com/macros/s/YOU_DATA_URL/exec"; 126 | HttpGet get = new HttpGet(URL); 127 | HttpResponse rp = hc.execute(get); 128 | if(rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) 129 | { 130 | String result = EntityUtils.toString(rp.getEntity()); 131 | JSONArray objects = new JSONArray(result); 132 | 133 | for (int i = 0; i < objects.length(); i++) { 134 | JSONObject session = objects.getJSONObject(i); 135 | Item item = new Item(); 136 | item.name = session.getString("item"); 137 | item.count = session.getString("quantity"); 138 | items.add(item); 139 | } 140 | } 141 | } catch (Exception e) { 142 | Log.e("ItemFeed", "Error loading JSON", e); 143 | } 144 | return null; 145 | } 146 | 147 | @Override 148 | protected void onPostExecute(Void result) { 149 | SimpleArrayAdapter adapter = new SimpleArrayAdapter(DemoActivity.this,items); 150 | setListAdapter(adapter); 151 | } 152 | } 153 | 154 | @Override 155 | public boolean onCreateOptionsMenu(Menu menu) { 156 | MenuInflater inflater = getMenuInflater(); 157 | inflater.inflate(R.menu.options_menu, menu); 158 | return true; 159 | } 160 | 161 | @Override 162 | public boolean onOptionsItemSelected(MenuItem item) { 163 | switch(item.getItemId()) { 164 | /* 165 | * Typically, an application registers automatically, so options 166 | * below are disabled. Uncomment them if you want to manually 167 | * register or unregister the device (you will also need to 168 | * uncomment the equivalent options on options_menu.xml). 169 | */ 170 | 171 | case R.id.options_register: 172 | GCMRegistrar.register(this, SENDER_ID); 173 | return true; 174 | case R.id.options_unregister: 175 | GCMRegistrar.unregister(this); 176 | return true; 177 | 178 | case R.id.options_clear: 179 | mDisplay.setText(null); 180 | return true; 181 | case R.id.options_exit: 182 | finish(); 183 | return true; 184 | case R.id.options_refresh_data: 185 | MyTask task = new MyTask(); 186 | task.execute(); 187 | return true; 188 | default: 189 | return super.onOptionsItemSelected(item); 190 | } 191 | } 192 | 193 | @Override 194 | protected void onDestroy() { 195 | if (mRegisterTask != null) { 196 | mRegisterTask.cancel(true); 197 | } 198 | unregisterReceiver(mHandleMessageReceiver); 199 | GCMRegistrar.onDestroy(this); 200 | super.onDestroy(); 201 | } 202 | 203 | private void checkNotNull(Object reference, String name) { 204 | if (reference == null) { 205 | throw new NullPointerException( 206 | getString(R.string.error_config, name)); 207 | } 208 | } 209 | 210 | private final BroadcastReceiver mHandleMessageReceiver = 211 | new BroadcastReceiver() { 212 | @Override 213 | public void onReceive(Context context, Intent intent) { 214 | String newMessage = intent.getExtras().getString(EXTRA_MESSAGE); 215 | mDisplay.append(newMessage + "\n"); 216 | 217 | MyTask task = new MyTask(); 218 | task.execute(); 219 | } 220 | }; 221 | 222 | } -------------------------------------------------------------------------------- /Android/ClientCode/src/com/google/android/gcm/demo/app/GCMIntentService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.gcm.demo.app; 17 | 18 | import static com.google.android.gcm.demo.app.CommonUtilities.SENDER_ID; 19 | import static com.google.android.gcm.demo.app.CommonUtilities.displayMessage; 20 | 21 | import android.annotation.TargetApi; 22 | import android.app.Notification; 23 | import android.app.NotificationManager; 24 | import android.app.PendingIntent; 25 | import android.content.Context; 26 | import android.content.Intent; 27 | import android.os.Build; 28 | import android.util.Log; 29 | 30 | import com.google.android.gcm.GCMBaseIntentService; 31 | import com.google.android.gcm.GCMRegistrar; 32 | 33 | /** 34 | * IntentService responsible for handling GCM messages. 35 | */ 36 | public class GCMIntentService extends GCMBaseIntentService { 37 | 38 | @SuppressWarnings("hiding") 39 | private static final String TAG = "GCMIntentService"; 40 | 41 | public GCMIntentService() { 42 | super(SENDER_ID); 43 | } 44 | 45 | @Override 46 | protected void onRegistered(Context context, String registrationId) { 47 | Log.i(TAG, "Device registered: regId = " + registrationId); 48 | displayMessage(context, getString(R.string.gcm_registered)); 49 | ServerUtilities.register(context, registrationId); 50 | } 51 | 52 | @Override 53 | protected void onUnregistered(Context context, String registrationId) { 54 | Log.i(TAG, "Device unregistered"); 55 | displayMessage(context, getString(R.string.gcm_unregistered)); 56 | if (GCMRegistrar.isRegisteredOnServer(context)) { 57 | ServerUtilities.unregister(context, registrationId); 58 | } else { 59 | // This callback results from the call to unregister made on 60 | // ServerUtilities when the registration to the server failed. 61 | Log.i(TAG, "Ignoring unregister callback"); 62 | } 63 | } 64 | 65 | @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) 66 | @Override 67 | protected void onMessage(Context context, Intent intent) { 68 | Log.i(TAG, "Received message"); 69 | String message = intent.getExtras().getString("message", getString(R.string.gcm_message)); 70 | displayMessage(context, message); 71 | // notifies user 72 | generateNotification(context, message); 73 | } 74 | 75 | @Override 76 | protected void onDeletedMessages(Context context, int total) { 77 | Log.i(TAG, "Received deleted messages notification"); 78 | String message = getString(R.string.gcm_deleted, total); 79 | displayMessage(context, message); 80 | // notifies user 81 | generateNotification(context, message); 82 | } 83 | 84 | @Override 85 | public void onError(Context context, String errorId) { 86 | Log.i(TAG, "Received error: " + errorId); 87 | displayMessage(context, getString(R.string.gcm_error, errorId)); 88 | } 89 | 90 | @Override 91 | protected boolean onRecoverableError(Context context, String errorId) { 92 | // log message 93 | Log.i(TAG, "Received recoverable error: " + errorId); 94 | displayMessage(context, getString(R.string.gcm_recoverable_error, 95 | errorId)); 96 | return super.onRecoverableError(context, errorId); 97 | } 98 | 99 | /** 100 | * Issues a notification to inform the user that server has sent a message. 101 | */ 102 | private static void generateNotification(Context context, String message) { 103 | int icon = R.drawable.ic_stat_gcm; 104 | long when = System.currentTimeMillis(); 105 | NotificationManager notificationManager = (NotificationManager) 106 | context.getSystemService(Context.NOTIFICATION_SERVICE); 107 | Notification notification = new Notification(icon, message, when); 108 | String title = context.getString(R.string.app_name); 109 | Intent notificationIntent = new Intent(context, DemoActivity.class); 110 | // set intent so it does not start a new activity 111 | notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | 112 | Intent.FLAG_ACTIVITY_SINGLE_TOP); 113 | PendingIntent intent = 114 | PendingIntent.getActivity(context, 0, notificationIntent, 0); 115 | notification.setLatestEventInfo(context, title, message, intent); 116 | notification.flags |= Notification.FLAG_AUTO_CANCEL; 117 | notification.defaults = Notification.DEFAULT_ALL; 118 | notificationManager.notify(0, notification); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /Android/ClientCode/src/com/google/android/gcm/demo/app/Item.java: -------------------------------------------------------------------------------- 1 | package com.google.android.gcm.demo.app; 2 | 3 | public class Item { 4 | String name; 5 | String count; 6 | } 7 | -------------------------------------------------------------------------------- /Android/ClientCode/src/com/google/android/gcm/demo/app/ServerUtilities.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.android.gcm.demo.app; 17 | 18 | import static com.google.android.gcm.demo.app.CommonUtilities.SERVER_URL; 19 | import static com.google.android.gcm.demo.app.CommonUtilities.TAG; 20 | import static com.google.android.gcm.demo.app.CommonUtilities.displayMessage; 21 | 22 | import com.google.android.gcm.GCMRegistrar; 23 | 24 | import android.content.Context; 25 | import android.util.Log; 26 | 27 | import java.io.IOException; 28 | import java.io.OutputStream; 29 | import java.net.HttpURLConnection; 30 | import java.net.MalformedURLException; 31 | import java.net.URL; 32 | import java.util.HashMap; 33 | import java.util.Iterator; 34 | import java.util.Map; 35 | import java.util.Map.Entry; 36 | import java.util.Random; 37 | 38 | /** 39 | * Helper class used to communicate with the demo server. 40 | */ 41 | public final class ServerUtilities { 42 | 43 | private static final int MAX_ATTEMPTS = 5; 44 | private static final int BACKOFF_MILLI_SECONDS = 2000; 45 | private static final Random random = new Random(); 46 | 47 | /** 48 | * Register this account/device pair within the server. 49 | * 50 | * @return whether the registration succeeded or not. 51 | */ 52 | static boolean register(final Context context, final String regId) { 53 | Log.i(TAG, "registering device (regId = " + regId + ")"); 54 | String serverUrl = SERVER_URL;// + "/register"; 55 | Map params = new HashMap(); 56 | params.put("regId", regId); 57 | params.put("type", "register"); 58 | long backoff = BACKOFF_MILLI_SECONDS + random.nextInt(1000); 59 | // Once GCM returns a registration id, we need to register it in the 60 | // demo server. As the server might be down, we will retry it a couple 61 | // times. 62 | for (int i = 1; i <= MAX_ATTEMPTS; i++) { 63 | Log.d(TAG, "Attempt #" + i + " to register"); 64 | try { 65 | displayMessage(context, context.getString( 66 | R.string.server_registering, i, MAX_ATTEMPTS)); 67 | post(serverUrl, params); 68 | GCMRegistrar.setRegisteredOnServer(context, true); 69 | String message = context.getString(R.string.server_registered); 70 | CommonUtilities.displayMessage(context, message); 71 | return true; 72 | } catch (IOException e) { 73 | // Here we are simplifying and retrying on any error; in a real 74 | // application, it should retry only on unrecoverable errors 75 | // (like HTTP error code 503). 76 | Log.e(TAG, "Failed to register on attempt " + i, e); 77 | if (i == MAX_ATTEMPTS) { 78 | break; 79 | } 80 | try { 81 | Log.d(TAG, "Sleeping for " + backoff + " ms before retry"); 82 | Thread.sleep(backoff); 83 | } catch (InterruptedException e1) { 84 | // Activity finished before we complete - exit. 85 | Log.d(TAG, "Thread interrupted: abort remaining retries!"); 86 | Thread.currentThread().interrupt(); 87 | return false; 88 | } 89 | // increase backoff exponentially 90 | backoff *= 2; 91 | } 92 | } 93 | String message = context.getString(R.string.server_register_error, 94 | MAX_ATTEMPTS); 95 | CommonUtilities.displayMessage(context, message); 96 | return false; 97 | } 98 | 99 | /** 100 | * Unregister this account/device pair within the server. 101 | */ 102 | static void unregister(final Context context, final String regId) { 103 | Log.i(TAG, "unregistering device (regId = " + regId + ")"); 104 | String serverUrl = SERVER_URL;// + "/unregister"; 105 | Map params = new HashMap(); 106 | params.put("regId", regId); 107 | params.put("type","unregister"); 108 | try { 109 | post(serverUrl, params); 110 | GCMRegistrar.setRegisteredOnServer(context, false); 111 | String message = context.getString(R.string.server_unregistered); 112 | CommonUtilities.displayMessage(context, message); 113 | } catch (IOException e) { 114 | // At this point the device is unregistered from GCM, but still 115 | // registered in the server. 116 | // We could try to unregister again, but it is not necessary: 117 | // if the server tries to send a message to the device, it will get 118 | // a "NotRegistered" error message and should unregister the device. 119 | String message = context.getString(R.string.server_unregister_error, 120 | e.getMessage()); 121 | CommonUtilities.displayMessage(context, message); 122 | } 123 | } 124 | 125 | /** 126 | * Issue a POST request to the server. 127 | * 128 | * @param endpoint POST address. 129 | * @param params request parameters. 130 | * 131 | * @throws IOException propagated from POST. 132 | */ 133 | private static void post(String endpoint, Map params) 134 | throws IOException { 135 | URL url; 136 | try { 137 | url = new URL(endpoint); 138 | } catch (MalformedURLException e) { 139 | throw new IllegalArgumentException("invalid url: " + endpoint); 140 | } 141 | StringBuilder bodyBuilder = new StringBuilder(); 142 | Iterator> iterator = params.entrySet().iterator(); 143 | // constructs the POST body using the parameters 144 | while (iterator.hasNext()) { 145 | Entry param = iterator.next(); 146 | bodyBuilder.append(param.getKey()).append('=') 147 | .append(param.getValue()); 148 | if (iterator.hasNext()) { 149 | bodyBuilder.append('&'); 150 | } 151 | } 152 | String body = bodyBuilder.toString(); 153 | Log.v(TAG, "Posting '" + body + "' to " + url); 154 | byte[] bytes = body.getBytes(); 155 | HttpURLConnection conn = null; 156 | try { 157 | conn = (HttpURLConnection) url.openConnection(); 158 | conn.setDoOutput(true); 159 | conn.setUseCaches(false); 160 | conn.setFixedLengthStreamingMode(bytes.length); 161 | conn.setRequestMethod("POST"); 162 | conn.setRequestProperty("Content-Type", 163 | "application/x-www-form-urlencoded;charset=UTF-8"); 164 | // post the request 165 | OutputStream out = conn.getOutputStream(); 166 | out.write(bytes); 167 | out.close(); 168 | // handle the response 169 | int status = conn.getResponseCode(); 170 | if (status != 200) { 171 | throw new IOException("Post failed with error code " + status); 172 | } 173 | } finally { 174 | if (conn != null) { 175 | conn.disconnect(); 176 | } 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Android/ClientCode/src/com/google/android/gcm/demo/app/SimpleArrayAdapter.java: -------------------------------------------------------------------------------- 1 | package com.google.android.gcm.demo.app; 2 | 3 | import java.util.ArrayList; 4 | 5 | import android.content.Context; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ArrayAdapter; 10 | import android.widget.TextView; 11 | 12 | public class SimpleArrayAdapter extends ArrayAdapter { 13 | private final Context context; 14 | private final ArrayList values; 15 | 16 | public SimpleArrayAdapter(Context context, ArrayList values) { 17 | super(context, R.layout.rowlayout, values); 18 | this.context = context; 19 | this.values = values; 20 | } 21 | 22 | @Override 23 | public View getView(int position, View convertView, ViewGroup parent) { 24 | LayoutInflater inflater = (LayoutInflater) context 25 | .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 26 | View rowView = inflater.inflate(R.layout.rowlayout, parent, false); 27 | Item i = values.get(position); 28 | 29 | 30 | TextView nameTextView = (TextView) rowView.findViewById(R.id.inventoryName); 31 | TextView countTextView = (TextView) rowView.findViewById(R.id.inventoryCount); 32 | nameTextView.setText(i.name); 33 | 34 | countTextView.setText(i.count); 35 | 36 | return rowView; 37 | } 38 | } -------------------------------------------------------------------------------- /Android/GCM_Registrar_Script/code.gs: -------------------------------------------------------------------------------- 1 | function doGet() { 2 | return HtmlService.createHtmlOutputFromFile('ui'); 3 | } 4 | 5 | 6 | //import shared db library 7 | //used the shared version intead of - 8 | //ScriptDb.getMyDb() 9 | var db = SharedDb.getDb(); 10 | 11 | function doPost(e) { 12 | if(e.parameter.regId){ 13 | var reg = db.query({regId : e.parameter.regId}).next(); 14 | if(reg && e.parameter.type === 'unregister'){ 15 | db.remove(reg); 16 | } 17 | else if(!reg && e.parameter.type === 'register'){ 18 | db.save(e.parameter); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Android/GCM_Registrar_Script/scriptdb.gs: -------------------------------------------------------------------------------- 1 | var db = SharedDb.getDb(); 2 | 3 | 4 | function saveObjToDb(obj){ 5 | try{ 6 | var savedObj = db.save(obj); 7 | return "Saved ID - " + savedObj.getId(); 8 | }catch(e){ 9 | log(e); 10 | throw(e); 11 | } 12 | } 13 | 14 | function getCount(query){ 15 | try{ 16 | return "Current count for query is - " + db.count(query); 17 | }catch(e){ 18 | log(e); 19 | throw(e); 20 | } 21 | } 22 | 23 | function loadIDsFromDb(idlist){ 24 | try{ 25 | return db.load(idlist); 26 | }catch(e){ 27 | log(e); 28 | throw(e); 29 | } 30 | } 31 | 32 | function deleteByIds(idlist){ 33 | var obs_to_remove = loadIDsFromDb(idlist); 34 | var results = db.removeBatch(obs_to_remove, false); 35 | if (db.allOk(results)) { 36 | return "Delete by IDs successfull!"; 37 | } 38 | var failedObs = []; 39 | for (var i = 0; i < results.length; i++) { 40 | if (!results[i].successful()) { 41 | failedObs.push(obs_to_remove[i]); 42 | } 43 | } 44 | return "Failed to delete " + failedObs.length + " item(s) out of " + results.length; 45 | } 46 | 47 | function queryFromDb(query){ 48 | try{ 49 | var result = db.query(query); 50 | var response = {}; 51 | while (result.hasNext()) { 52 | var current = result.next(); 53 | response[current.getId()] = current; 54 | } 55 | return response; 56 | }catch(e){ 57 | log(e); 58 | throw(e); 59 | } 60 | } 61 | 62 | function deleteAll(query) { 63 | try{ 64 | while (true) { 65 | var result = db.query(query); 66 | if (result.getSize() == 0) { 67 | break; 68 | } 69 | while (result.hasNext()) { 70 | db.remove(result.next()); 71 | } 72 | } 73 | return "Delete for specified query successful!"; 74 | }catch(e){ 75 | log(e); 76 | throw(e); 77 | } 78 | } 79 | 80 | function log(msg){ 81 | //write to a logger here 82 | //DocsList.getFileById('0B0JNj_IM2wiPMS1lZTFhZjVjNC0yZTBjLTRiOWItYWVhMy0yYTU1ZjdkMGVkMGE').append(msg+'\n'); 83 | } -------------------------------------------------------------------------------- /Android/GCM_Registrar_Script/ui.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 90 | 91 |

Simple ScriptDb tester

92 | 93 | Enter a JavaScript Object to store, a ScriptDb query to run or IDs to lookup and press the appropriate button below
94 |
95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |

103 |
104 |

105 | By Arun Nagarajan 106 | 107 | -------------------------------------------------------------------------------- /Android/InventoryContentService.gs: -------------------------------------------------------------------------------- 1 | function doGet() { 2 | var ss = SpreadsheetApp.openById('YOUR_SS_ID'); 3 | var sheet = ss.getSheets()[0]; 4 | 5 | // Get the range of cells that store employee data. 6 | var employeeDataRange = ss.getRangeByName("inventoryData"); 7 | 8 | // For every row of employee data, generate an employee object. 9 | var employeeObjects = getRowsData(sheet, employeeDataRange); 10 | return ContentService.createTextOutput(JSON.stringify(employeeObjects)).setMimeType(ContentService.MimeType.JSON); 11 | } 12 | 13 | 14 | // getRowsData iterates row by row in the input range and returns an array of objects. 15 | // Each object contains all the data for a given row, indexed by its normalized column name. 16 | // Arguments: 17 | // - sheet: the sheet object that contains the data to be processed 18 | // - range: the exact range of cells where the data is stored 19 | // - columnHeadersRowIndex: specifies the row number where the column names are stored. 20 | // This argument is optional and it defaults to the row immediately above range; 21 | // Returns an Array of objects. 22 | function getRowsData(sheet, range, columnHeadersRowIndex) { 23 | columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1; 24 | var numColumns = range.getLastColumn() - range.getColumn() + 1; 25 | var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns); 26 | var headers = headersRange.getValues()[0]; 27 | return getObjects(range.getValues(), normalizeHeaders(headers)); 28 | } 29 | 30 | 31 | // For every row of data in data, generates an object that contains the data. Names of 32 | // object fields are defined in keys. 33 | // Arguments: 34 | // - data: JavaScript 2d array 35 | // - keys: Array of Strings that define the property names for the objects to create 36 | function getObjects(data, keys) { 37 | var objects = []; 38 | for (var i = 0; i < data.length; ++i) { 39 | var object = {}; 40 | var hasData = false; 41 | for (var j = 0; j < data[i].length; ++j) { 42 | var cellData = data[i][j]; 43 | if (isCellEmpty(cellData)) { 44 | continue; 45 | } 46 | object[keys[j]] = cellData; 47 | hasData = true; 48 | } 49 | if (hasData) { 50 | objects.push(object); 51 | } 52 | } 53 | return objects; 54 | } 55 | 56 | // Returns an Array of normalized Strings. 57 | // Arguments: 58 | // - headers: Array of Strings to normalize 59 | function normalizeHeaders(headers) { 60 | var keys = []; 61 | for (var i = 0; i < headers.length; ++i) { 62 | var key = normalizeHeader(headers[i]); 63 | if (key.length > 0) { 64 | keys.push(key); 65 | } 66 | } 67 | return keys; 68 | } 69 | 70 | // Normalizes a string, by removing all alphanumeric characters and using mixed case 71 | // to separate words. The output will always start with a lower case letter. 72 | // This function is designed to produce JavaScript object property names. 73 | // Arguments: 74 | // - header: string to normalize 75 | // Examples: 76 | // "First Name" -> "firstName" 77 | // "Market Cap (millions) -> "marketCapMillions 78 | // "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored" 79 | function normalizeHeader(header) { 80 | var key = ""; 81 | var upperCase = false; 82 | for (var i = 0; i < header.length; ++i) { 83 | var letter = header[i]; 84 | if (letter == " " && key.length > 0) { 85 | upperCase = true; 86 | continue; 87 | } 88 | if (!isAlnum(letter)) { 89 | continue; 90 | } 91 | if (key.length == 0 && isDigit(letter)) { 92 | continue; // first character must be a letter 93 | } 94 | if (upperCase) { 95 | upperCase = false; 96 | key += letter.toUpperCase(); 97 | } else { 98 | key += letter.toLowerCase(); 99 | } 100 | } 101 | return key; 102 | } 103 | 104 | // Returns true if the cell where cellData was read from is empty. 105 | // Arguments: 106 | // - cellData: string 107 | function isCellEmpty(cellData) { 108 | return typeof(cellData) == "string" && cellData == ""; 109 | } 110 | 111 | // Returns true if the character char is alphabetical, false otherwise. 112 | function isAlnum(char) { 113 | return char >= 'A' && char <= 'Z' || 114 | char >= 'a' && char <= 'z' || 115 | isDigit(char); 116 | } 117 | 118 | // Returns true if the character char is a digit, false otherwise. 119 | function isDigit(char) { 120 | return char >= '0' && char <= '9'; 121 | } 122 | 123 | // Given a JavaScript 2d Array, this function returns the transposed table. 124 | // Arguments: 125 | // - data: JavaScript 2d Array 126 | // Returns a JavaScript 2d Array 127 | // Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]]. 128 | function arrayTranspose(data) { 129 | if (data.length == 0 || data[0].length == 0) { 130 | return null; 131 | } 132 | 133 | var ret = []; 134 | for (var i = 0; i < data[0].length; ++i) { 135 | ret.push([]); 136 | } 137 | 138 | for (var i = 0; i < data.length; ++i) { 139 | for (var j = 0; j < data[i].length; ++j) { 140 | ret[j][i] = data[i][j]; 141 | } 142 | } 143 | 144 | return ret; 145 | } 146 | -------------------------------------------------------------------------------- /Android/SharedDb.gs: -------------------------------------------------------------------------------- 1 | var db = ScriptDb.getMyDb(); 2 | 3 | function getDb(){ 4 | return db; 5 | } 6 | 7 | function sendGCM(msg){ 8 | msg = msg || 'hello world!'; //give default message for debugging 9 | 10 | var regIds = []; 11 | var result = db.query({}); 12 | while (result.hasNext()) { 13 | var current = result.next(); 14 | regIds.push(current.regId); 15 | } 16 | 17 | var apiKey = 'YOUR_API_KEY'; 18 | 19 | var payload = {'registration_ids' : regIds, 20 | 'data' : { 21 | 'message' : msg 22 | }}; 23 | var urlFetchOptions = {'contentType' : 'application/json', 24 | 'headers' : {'Authorization' : 'key=' + apiKey}, 25 | 'method' : 'post', 26 | 'payload' : JSON.stringify(payload)}; 27 | 28 | var gcmUrl = 'https://android.googleapis.com/gcm/send'; 29 | var response = UrlFetchApp.fetch(gcmUrl,urlFetchOptions).getContentText() 30 | 31 | Logger.log(response);//for testing purposes. improve error handling here 32 | } -------------------------------------------------------------------------------- /Android/readme.md: -------------------------------------------------------------------------------- 1 | Click the image below for video with more details - 2 | 3 | [![ScreenShot](https://i.ytimg.com/vi/RSgMEtRl0sw/hqdefault.jpg)](http://www.youtube.com/watch?v=RSgMEtRl0sw&list=PL68F511F6E3C122EB) -------------------------------------------------------------------------------- /Android/track-and-notify.gs: -------------------------------------------------------------------------------- 1 | //create an onEdit trigger for this! in a spreadsheet bound script 2 | //import shared db library 3 | function sendNotification(){ 4 | var msg = "Edited at: " + new Date().toTimeString(); 5 | SharedDb.sendGCM(msg); 6 | } 7 | -------------------------------------------------------------------------------- /ContentService/Addressbook-VCF/Code.gs: -------------------------------------------------------------------------------- 1 | function doGet() { 2 | var contact = ContactsApp.getContact('youremail@gmail.com'); 3 | 4 | var t = HtmlService.createTemplateFromFile('vcard_template'); 5 | t.contact = contact; 6 | var output = t.evaluate().getContent(); 7 | 8 | return ContentService.createTextOutput(output).downloadAsFile('ContactCard.vcf'); 9 | } 10 | -------------------------------------------------------------------------------- /ContentService/Addressbook-VCF/vcard_template.html: -------------------------------------------------------------------------------- 1 | BEGIN:VCARD 2 | FN: 3 | TITLE:General Manager 4 | ORG:;North American Division;Manufacturing 5 | ADR;POSTAL;WORK:;;P.O. Box 10010;AnyCity;AnyState;00000;U.S.A. 6 | LABEL;POSTAL;WORK;ENCODING=QUOTED-PRINTABLE:P.O. Box 10010=0D=0A= 7 | Anywhere, TN 37849=0D=0A=U.S.A. 8 | ADR;PARCEL;WORK:;133 Anywhere St.;Suite 360;AnyCity;AnyState;00000;U.S.A. 9 | LABEL;POSTAL;WORK;ENCODING=QUOTED-PRINTABLE:133 Anywhere St.=0D=0A= 10 | Anywhere, TN 37849=0D=0A=U.S.A. 11 | TEL;Work;VOICE;MESG;PREF:+1-234-456-7891 x56473 12 | TEL;Home:+1-234-456-7891 13 | TEL;Pager:+1-234-456-7891 14 | TEL;Cell:+1-234-456-7891 15 | TEL;Modem;FAX:+1-234-456-7891,,*3 16 | EMAIL;Internet:anywhere@anywhere.com 17 | URL:http://www.anywhere.com/mrh.vcf 18 | UID:http://www.anywhere.com/mrh.vcf 19 | TZ:-0500 20 | BDAY:1997-11-29 21 | REV:20090401T065518 22 | VERSION:2.1 23 | END:VCARD -------------------------------------------------------------------------------- /ContentService/Gmail-RSS/code.gs: -------------------------------------------------------------------------------- 1 | function doGet() { 2 | var content = HtmlService.createTemplateFromFile('rss').evaluate().getContent(); 3 | return ContentService.createTextOutput(content).setMimeType(ContentService.MimeType.RSS); 4 | } 5 | -------------------------------------------------------------------------------- /ContentService/Gmail-RSS/rss.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Gmail Feed 4 | 7 | 8 | <?= threads[t].getFirstMessageSubject() ?> 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Default-568h@2x.png -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 69BB74F716B2D0C500EC6AF4 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 69BB74F616B2D0C500EC6AF4 /* Default-568h@2x.png */; }; 11 | 6F4DFB9E14BA2B2200B110D2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F4DFB9D14BA2B2200B110D2 /* UIKit.framework */; }; 12 | 6F4DFBA014BA2B2200B110D2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F4DFB9F14BA2B2200B110D2 /* Foundation.framework */; }; 13 | 6F4DFBA214BA2B2200B110D2 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F4DFBA114BA2B2200B110D2 /* CoreGraphics.framework */; }; 14 | 6F4DFBA814BA2B2200B110D2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6F4DFBA614BA2B2200B110D2 /* InfoPlist.strings */; }; 15 | 6F4DFBAA14BA2B2200B110D2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DFBA914BA2B2200B110D2 /* main.m */; }; 16 | 6F4DFBAE14BA2B2200B110D2 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DFBAD14BA2B2200B110D2 /* AppDelegate.m */; }; 17 | 6F4DFBB114BA2B2200B110D2 /* MainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6F4DFBAF14BA2B2200B110D2 /* MainStoryboard.storyboard */; }; 18 | 6F4DFBB414BA2B2200B110D2 /* MasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DFBB314BA2B2200B110D2 /* MasterViewController.m */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 69BB74F616B2D0C500EC6AF4 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 23 | 6F4DFB9914BA2B2200B110D2 /* Inventory.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Inventory.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 6F4DFB9D14BA2B2200B110D2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 25 | 6F4DFB9F14BA2B2200B110D2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 26 | 6F4DFBA114BA2B2200B110D2 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 27 | 6F4DFBA514BA2B2200B110D2 /* Inventory-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Inventory-Info.plist"; sourceTree = ""; }; 28 | 6F4DFBA714BA2B2200B110D2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 29 | 6F4DFBA914BA2B2200B110D2 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 30 | 6F4DFBAB14BA2B2200B110D2 /* Inventory-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Inventory-Prefix.pch"; sourceTree = ""; }; 31 | 6F4DFBAC14BA2B2200B110D2 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 32 | 6F4DFBAD14BA2B2200B110D2 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 33 | 6F4DFBB014BA2B2200B110D2 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard.storyboard; sourceTree = ""; }; 34 | 6F4DFBB214BA2B2200B110D2 /* MasterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MasterViewController.h; sourceTree = ""; }; 35 | 6F4DFBB314BA2B2200B110D2 /* MasterViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MasterViewController.m; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | 6F4DFB9614BA2B2200B110D2 /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | 6F4DFB9E14BA2B2200B110D2 /* UIKit.framework in Frameworks */, 44 | 6F4DFBA014BA2B2200B110D2 /* Foundation.framework in Frameworks */, 45 | 6F4DFBA214BA2B2200B110D2 /* CoreGraphics.framework in Frameworks */, 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | 6F4DFB8E14BA2B2200B110D2 = { 53 | isa = PBXGroup; 54 | children = ( 55 | 69BB74F616B2D0C500EC6AF4 /* Default-568h@2x.png */, 56 | 6F4DFBA314BA2B2200B110D2 /* InventorySample */, 57 | 6F4DFB9C14BA2B2200B110D2 /* Frameworks */, 58 | 6F4DFB9A14BA2B2200B110D2 /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | 6F4DFB9A14BA2B2200B110D2 /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 6F4DFB9914BA2B2200B110D2 /* Inventory.app */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | 6F4DFB9C14BA2B2200B110D2 /* Frameworks */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 6F4DFB9D14BA2B2200B110D2 /* UIKit.framework */, 74 | 6F4DFB9F14BA2B2200B110D2 /* Foundation.framework */, 75 | 6F4DFBA114BA2B2200B110D2 /* CoreGraphics.framework */, 76 | ); 77 | name = Frameworks; 78 | sourceTree = ""; 79 | }; 80 | 6F4DFBA314BA2B2200B110D2 /* InventorySample */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 6F4DFBAC14BA2B2200B110D2 /* AppDelegate.h */, 84 | 6F4DFBAD14BA2B2200B110D2 /* AppDelegate.m */, 85 | 6F4DFBAF14BA2B2200B110D2 /* MainStoryboard.storyboard */, 86 | 6F4DFBB214BA2B2200B110D2 /* MasterViewController.h */, 87 | 6F4DFBB314BA2B2200B110D2 /* MasterViewController.m */, 88 | 6F4DFBA414BA2B2200B110D2 /* Supporting Files */, 89 | ); 90 | name = InventorySample; 91 | path = Inventory; 92 | sourceTree = ""; 93 | }; 94 | 6F4DFBA414BA2B2200B110D2 /* Supporting Files */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 6F4DFBA514BA2B2200B110D2 /* Inventory-Info.plist */, 98 | 6F4DFBA614BA2B2200B110D2 /* InfoPlist.strings */, 99 | 6F4DFBA914BA2B2200B110D2 /* main.m */, 100 | 6F4DFBAB14BA2B2200B110D2 /* Inventory-Prefix.pch */, 101 | ); 102 | name = "Supporting Files"; 103 | sourceTree = ""; 104 | }; 105 | /* End PBXGroup section */ 106 | 107 | /* Begin PBXNativeTarget section */ 108 | 6F4DFB9814BA2B2200B110D2 /* Inventory */ = { 109 | isa = PBXNativeTarget; 110 | buildConfigurationList = 6F4DFBBA14BA2B2200B110D2 /* Build configuration list for PBXNativeTarget "Inventory" */; 111 | buildPhases = ( 112 | 6F4DFB9514BA2B2200B110D2 /* Sources */, 113 | 6F4DFB9614BA2B2200B110D2 /* Frameworks */, 114 | 6F4DFB9714BA2B2200B110D2 /* Resources */, 115 | ); 116 | buildRules = ( 117 | ); 118 | dependencies = ( 119 | ); 120 | name = Inventory; 121 | productName = "Twitter Test"; 122 | productReference = 6F4DFB9914BA2B2200B110D2 /* Inventory.app */; 123 | productType = "com.apple.product-type.application"; 124 | }; 125 | /* End PBXNativeTarget section */ 126 | 127 | /* Begin PBXProject section */ 128 | 6F4DFB9014BA2B2200B110D2 /* Project object */ = { 129 | isa = PBXProject; 130 | attributes = { 131 | LastUpgradeCheck = 0420; 132 | }; 133 | buildConfigurationList = 6F4DFB9314BA2B2200B110D2 /* Build configuration list for PBXProject "Inventory" */; 134 | compatibilityVersion = "Xcode 3.2"; 135 | developmentRegion = English; 136 | hasScannedForEncodings = 0; 137 | knownRegions = ( 138 | en, 139 | ); 140 | mainGroup = 6F4DFB8E14BA2B2200B110D2; 141 | productRefGroup = 6F4DFB9A14BA2B2200B110D2 /* Products */; 142 | projectDirPath = ""; 143 | projectRoot = ""; 144 | targets = ( 145 | 6F4DFB9814BA2B2200B110D2 /* Inventory */, 146 | ); 147 | }; 148 | /* End PBXProject section */ 149 | 150 | /* Begin PBXResourcesBuildPhase section */ 151 | 6F4DFB9714BA2B2200B110D2 /* Resources */ = { 152 | isa = PBXResourcesBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | 6F4DFBA814BA2B2200B110D2 /* InfoPlist.strings in Resources */, 156 | 6F4DFBB114BA2B2200B110D2 /* MainStoryboard.storyboard in Resources */, 157 | 69BB74F716B2D0C500EC6AF4 /* Default-568h@2x.png in Resources */, 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | /* End PBXResourcesBuildPhase section */ 162 | 163 | /* Begin PBXSourcesBuildPhase section */ 164 | 6F4DFB9514BA2B2200B110D2 /* Sources */ = { 165 | isa = PBXSourcesBuildPhase; 166 | buildActionMask = 2147483647; 167 | files = ( 168 | 6F4DFBAA14BA2B2200B110D2 /* main.m in Sources */, 169 | 6F4DFBAE14BA2B2200B110D2 /* AppDelegate.m in Sources */, 170 | 6F4DFBB414BA2B2200B110D2 /* MasterViewController.m in Sources */, 171 | ); 172 | runOnlyForDeploymentPostprocessing = 0; 173 | }; 174 | /* End PBXSourcesBuildPhase section */ 175 | 176 | /* Begin PBXVariantGroup section */ 177 | 6F4DFBA614BA2B2200B110D2 /* InfoPlist.strings */ = { 178 | isa = PBXVariantGroup; 179 | children = ( 180 | 6F4DFBA714BA2B2200B110D2 /* en */, 181 | ); 182 | name = InfoPlist.strings; 183 | sourceTree = ""; 184 | }; 185 | 6F4DFBAF14BA2B2200B110D2 /* MainStoryboard.storyboard */ = { 186 | isa = PBXVariantGroup; 187 | children = ( 188 | 6F4DFBB014BA2B2200B110D2 /* en */, 189 | ); 190 | name = MainStoryboard.storyboard; 191 | sourceTree = ""; 192 | }; 193 | /* End PBXVariantGroup section */ 194 | 195 | /* Begin XCBuildConfiguration section */ 196 | 6F4DFBB814BA2B2200B110D2 /* Debug */ = { 197 | isa = XCBuildConfiguration; 198 | buildSettings = { 199 | ALWAYS_SEARCH_USER_PATHS = NO; 200 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 201 | CLANG_ENABLE_OBJC_ARC = YES; 202 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 203 | COPY_PHASE_STRIP = NO; 204 | GCC_C_LANGUAGE_STANDARD = gnu99; 205 | GCC_DYNAMIC_NO_PIC = NO; 206 | GCC_OPTIMIZATION_LEVEL = 0; 207 | GCC_PREPROCESSOR_DEFINITIONS = ( 208 | "DEBUG=1", 209 | "$(inherited)", 210 | ); 211 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 212 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 213 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 214 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 215 | GCC_WARN_UNUSED_VARIABLE = YES; 216 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 217 | SDKROOT = iphoneos; 218 | }; 219 | name = Debug; 220 | }; 221 | 6F4DFBB914BA2B2200B110D2 /* Release */ = { 222 | isa = XCBuildConfiguration; 223 | buildSettings = { 224 | ALWAYS_SEARCH_USER_PATHS = NO; 225 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 226 | CLANG_ENABLE_OBJC_ARC = YES; 227 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 228 | COPY_PHASE_STRIP = YES; 229 | GCC_C_LANGUAGE_STANDARD = gnu99; 230 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 231 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 232 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 233 | GCC_WARN_UNUSED_VARIABLE = YES; 234 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 235 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 236 | SDKROOT = iphoneos; 237 | VALIDATE_PRODUCT = YES; 238 | }; 239 | name = Release; 240 | }; 241 | 6F4DFBBB14BA2B2200B110D2 /* Debug */ = { 242 | isa = XCBuildConfiguration; 243 | buildSettings = { 244 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 245 | GCC_PREFIX_HEADER = "Inventory/Inventory-Prefix.pch"; 246 | INFOPLIST_FILE = "$(SRCROOT)/Inventory/Inventory-Info.plist"; 247 | PRODUCT_NAME = Inventory; 248 | WRAPPER_EXTENSION = app; 249 | }; 250 | name = Debug; 251 | }; 252 | 6F4DFBBC14BA2B2200B110D2 /* Release */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 256 | GCC_PREFIX_HEADER = "Inventory/Inventory-Prefix.pch"; 257 | INFOPLIST_FILE = "$(SRCROOT)/Inventory/Inventory-Info.plist"; 258 | PRODUCT_NAME = Inventory; 259 | WRAPPER_EXTENSION = app; 260 | }; 261 | name = Release; 262 | }; 263 | /* End XCBuildConfiguration section */ 264 | 265 | /* Begin XCConfigurationList section */ 266 | 6F4DFB9314BA2B2200B110D2 /* Build configuration list for PBXProject "Inventory" */ = { 267 | isa = XCConfigurationList; 268 | buildConfigurations = ( 269 | 6F4DFBB814BA2B2200B110D2 /* Debug */, 270 | 6F4DFBB914BA2B2200B110D2 /* Release */, 271 | ); 272 | defaultConfigurationIsVisible = 0; 273 | defaultConfigurationName = Release; 274 | }; 275 | 6F4DFBBA14BA2B2200B110D2 /* Build configuration list for PBXNativeTarget "Inventory" */ = { 276 | isa = XCConfigurationList; 277 | buildConfigurations = ( 278 | 6F4DFBBB14BA2B2200B110D2 /* Debug */, 279 | 6F4DFBBC14BA2B2200B110D2 /* Release */, 280 | ); 281 | defaultConfigurationIsVisible = 0; 282 | defaultConfigurationName = Release; 283 | }; 284 | /* End XCConfigurationList section */ 285 | }; 286 | rootObject = 6F4DFB9014BA2B2200B110D2 /* Project object */; 287 | } 288 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory.xcodeproj/project.xcworkspace/xcuserdata/anagarajan.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory.xcodeproj/project.xcworkspace/xcuserdata/anagarajan.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory.xcodeproj/project.xcworkspace/xcuserdata/anagarajan.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges 6 | 7 | SnapshotAutomaticallyBeforeSignificantChanges 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory.xcodeproj/xcuserdata/anagarajan.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory.xcodeproj/xcuserdata/anagarajan.xcuserdatad/xcschemes/InventorySample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory.xcodeproj/xcuserdata/anagarajan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | InventorySample.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 6F4DFB9814BA2B2200B110D2 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AppDelegate : UIResponder 4 | 5 | @property (strong, nonatomic) UIWindow *window; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | @implementation AppDelegate 4 | 5 | @synthesize window = _window; 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | // Override point for customization after application launch. 10 | return YES; 11 | } 12 | 13 | - (void)applicationWillResignActive:(UIApplication *)application 14 | { 15 | /* 16 | Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 17 | Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 18 | */ 19 | } 20 | 21 | - (void)applicationDidEnterBackground:(UIApplication *)application 22 | { 23 | /* 24 | Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 25 | If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 26 | */ 27 | } 28 | 29 | - (void)applicationWillEnterForeground:(UIApplication *)application 30 | { 31 | /* 32 | Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 33 | */ 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | /* 39 | Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 40 | */ 41 | } 42 | 43 | - (void)applicationWillTerminate:(UIApplication *)application 44 | { 45 | /* 46 | Called when the application is about to terminate. 47 | Save data if appropriate. 48 | See also applicationDidEnterBackground:. 49 | */ 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory/Inventory-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFiles 12 | 13 | CFBundleIdentifier 14 | entaq.${PRODUCT_NAME:rfc1034identifier} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1.0 27 | LSRequiresIPhoneOS 28 | 29 | UIMainStoryboardFile 30 | MainStoryboard 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory/Inventory-Prefix.pch: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #ifndef __IPHONE_5_0 4 | #warning "This project uses features only available in iOS SDK 5.0 and later." 5 | #endif 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory/MasterViewController.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | @interface MasterViewController : UITableViewController { 5 | NSArray *Items; 6 | } 7 | 8 | - (void)fetchItems; 9 | - (IBAction)refreshItems:(id)sender; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory/MasterViewController.m: -------------------------------------------------------------------------------- 1 | #import "MasterViewController.h" 2 | 3 | @implementation MasterViewController 4 | 5 | 6 | - (void)awakeFromNib 7 | { 8 | [super awakeFromNib]; 9 | } 10 | 11 | - (void)didReceiveMemoryWarning 12 | { 13 | [super didReceiveMemoryWarning]; 14 | // Release any cached data, images, etc that aren't in use. 15 | } 16 | 17 | #pragma mark - View lifecycle 18 | 19 | - (void)viewDidLoad 20 | { 21 | [super viewDidLoad]; 22 | [self fetchItems]; 23 | } 24 | 25 | - (void)fetchItems 26 | { 27 | NSString *ServiceURL = @"https://script.google.com/macros/s/AKfycbyonCfUtv-tbIAUO4okOYI8sUCQR-hnJGcOAq8QCQ_86XYc8qg/exec"; 28 | 29 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 30 | NSData* data = [NSData dataWithContentsOfURL: 31 | [NSURL URLWithString: ServiceURL]]; 32 | 33 | NSError* error; 34 | 35 | Items = [NSJSONSerialization JSONObjectWithData:data 36 | options:kNilOptions 37 | error:&error]; 38 | 39 | dispatch_async(dispatch_get_main_queue(), ^{ 40 | [self.tableView reloadData]; 41 | }); 42 | }); 43 | } 44 | 45 | - (IBAction)refreshItems:(id)sender { 46 | [self fetchItems]; 47 | } 48 | 49 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 50 | { 51 | return Items.count; 52 | } 53 | 54 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 55 | { 56 | static NSString *CellIdentifier = @"ItemCell"; 57 | 58 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 59 | if (cell == nil) { 60 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; 61 | } 62 | 63 | NSDictionary *Item = [Items objectAtIndex:indexPath.row]; 64 | NSString *text = [Item objectForKey:@"item"]; 65 | NSString *name = [Item objectForKey:@"quantity"]; 66 | 67 | cell.textLabel.text = text; 68 | cell.detailTextLabel.text = [NSString stringWithFormat:@"Current quantity %@", name]; 69 | 70 | return cell; 71 | } 72 | 73 | 74 | 75 | - (void)viewDidUnload 76 | { 77 | [super viewDidUnload]; 78 | // Release any retained subviews of the main view. 79 | // e.g. self.myOutlet = nil; 80 | } 81 | 82 | - (void)viewWillAppear:(BOOL)animated 83 | { 84 | [super viewWillAppear:animated]; 85 | } 86 | 87 | - (void)viewDidAppear:(BOOL)animated 88 | { 89 | [super viewDidAppear:animated]; 90 | } 91 | 92 | - (void)viewWillDisappear:(BOOL)animated 93 | { 94 | [super viewWillDisappear:animated]; 95 | } 96 | 97 | - (void)viewDidDisappear:(BOOL)animated 98 | { 99 | [super viewDidDisappear:animated]; 100 | } 101 | 102 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 103 | { 104 | // Return YES for supported orientations 105 | return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); 106 | } 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory/en.lproj/MainStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 45 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/Inventory-iOS/Inventory/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ContentService/Inventory-JSON-API-iOS/code.gs: -------------------------------------------------------------------------------- 1 | function doGet() { 2 | var ss = SpreadsheetApp.openById('0AiFUpEyZ-UKzdElxM3NpbFI4QUJwdmxqSXVlSjJmNmc'); 3 | var sheet = ss.getSheets()[0]; 4 | 5 | // Get the range of cells that store employee data. 6 | var employeeDataRange = ss.getRangeByName("inventoryData"); 7 | 8 | // For every row of employee data, generate an employee object. 9 | var employeeObjects = getRowsData(sheet, employeeDataRange); 10 | return ContentService.createTextOutput(JSON.stringify(employeeObjects)).setMimeType(ContentService.MimeType.JSON); 11 | } 12 | 13 | 14 | // getRowsData iterates row by row in the input range and returns an array of objects. 15 | // Each object contains all the data for a given row, indexed by its normalized column name. 16 | // Arguments: 17 | // - sheet: the sheet object that contains the data to be processed 18 | // - range: the exact range of cells where the data is stored 19 | // - columnHeadersRowIndex: specifies the row number where the column names are stored. 20 | // This argument is optional and it defaults to the row immediately above range; 21 | // Returns an Array of objects. 22 | function getRowsData(sheet, range, columnHeadersRowIndex) { 23 | columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1; 24 | var numColumns = range.getLastColumn() - range.getColumn() + 1; 25 | var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns); 26 | var headers = headersRange.getValues()[0]; 27 | return getObjects(range.getValues(), normalizeHeaders(headers)); 28 | } 29 | 30 | 31 | // For every row of data in data, generates an object that contains the data. Names of 32 | // object fields are defined in keys. 33 | // Arguments: 34 | // - data: JavaScript 2d array 35 | // - keys: Array of Strings that define the property names for the objects to create 36 | function getObjects(data, keys) { 37 | var objects = []; 38 | for (var i = 0; i < data.length; ++i) { 39 | var object = {}; 40 | var hasData = false; 41 | for (var j = 0; j < data[i].length; ++j) { 42 | var cellData = data[i][j]; 43 | if (isCellEmpty(cellData)) { 44 | continue; 45 | } 46 | object[keys[j]] = cellData; 47 | hasData = true; 48 | } 49 | if (hasData) { 50 | objects.push(object); 51 | } 52 | } 53 | return objects; 54 | } 55 | 56 | // Returns an Array of normalized Strings. 57 | // Arguments: 58 | // - headers: Array of Strings to normalize 59 | function normalizeHeaders(headers) { 60 | var keys = []; 61 | for (var i = 0; i < headers.length; ++i) { 62 | var key = normalizeHeader(headers[i]); 63 | if (key.length > 0) { 64 | keys.push(key); 65 | } 66 | } 67 | return keys; 68 | } 69 | 70 | // Normalizes a string, by removing all alphanumeric characters and using mixed case 71 | // to separate words. The output will always start with a lower case letter. 72 | // This function is designed to produce JavaScript object property names. 73 | // Arguments: 74 | // - header: string to normalize 75 | // Examples: 76 | // "First Name" -> "firstName" 77 | // "Market Cap (millions) -> "marketCapMillions 78 | // "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored" 79 | function normalizeHeader(header) { 80 | var key = ""; 81 | var upperCase = false; 82 | for (var i = 0; i < header.length; ++i) { 83 | var letter = header[i]; 84 | if (letter == " " && key.length > 0) { 85 | upperCase = true; 86 | continue; 87 | } 88 | if (!isAlnum(letter)) { 89 | continue; 90 | } 91 | if (key.length == 0 && isDigit(letter)) { 92 | continue; // first character must be a letter 93 | } 94 | if (upperCase) { 95 | upperCase = false; 96 | key += letter.toUpperCase(); 97 | } else { 98 | key += letter.toLowerCase(); 99 | } 100 | } 101 | return key; 102 | } 103 | 104 | // Returns true if the cell where cellData was read from is empty. 105 | // Arguments: 106 | // - cellData: string 107 | function isCellEmpty(cellData) { 108 | return typeof(cellData) == "string" && cellData == ""; 109 | } 110 | 111 | // Returns true if the character char is alphabetical, false otherwise. 112 | function isAlnum(char) { 113 | return char >= 'A' && char <= 'Z' || 114 | char >= 'a' && char <= 'z' || 115 | isDigit(char); 116 | } 117 | 118 | // Returns true if the character char is a digit, false otherwise. 119 | function isDigit(char) { 120 | return char >= '0' && char <= '9'; 121 | } 122 | 123 | // Given a JavaScript 2d Array, this function returns the transposed table. 124 | // Arguments: 125 | // - data: JavaScript 2d Array 126 | // Returns a JavaScript 2d Array 127 | // Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]]. 128 | function arrayTranspose(data) { 129 | if (data.length == 0 || data[0].length == 0) { 130 | return null; 131 | } 132 | 133 | var ret = []; 134 | for (var i = 0; i < data[0].length; ++i) { 135 | ret.push([]); 136 | } 137 | 138 | for (var i = 0; i < data.length; ++i) { 139 | for (var j = 0; j < data[i].length; ++j) { 140 | ret[j][i] = data[i][j]; 141 | } 142 | } 143 | 144 | return ret; 145 | } 146 | 147 | 148 | 149 | 150 | // Exercise: 151 | 152 | 153 | function runExercise() { 154 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 155 | var sheet = ss.getSheets()[1]; 156 | 157 | // Get the range of cells that store employee data. 158 | var employeeDataRange = sheet.getRange("B1:F5"); 159 | 160 | // For every row of employee data, generate an employee object. 161 | var employeeObjects = getColumnsData(sheet, employeeDataRange); 162 | 163 | var thirdEmployee = employeeObjects[2]; 164 | var stringToDisplay = "The third column is: " + thirdEmployee.firstName + " " + thirdEmployee.lastName; 165 | stringToDisplay += " (id #" + thirdEmployee.employeeId + ") working in the "; 166 | stringToDisplay += thirdEmployee.department + " department and with phone number "; 167 | stringToDisplay += thirdEmployee.phoneNumber; 168 | ss.msgBox(stringToDisplay); 169 | } 170 | 171 | // Given a JavaScript 2d Array, this function returns the transposed table. 172 | // Arguments: 173 | // - data: JavaScript 2d Array 174 | // Returns a JavaScript 2d Array 175 | // Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]]. 176 | function arrayTranspose(data) { 177 | if (data.length == 0 || data[0].length == 0) { 178 | return null; 179 | } 180 | 181 | var ret = []; 182 | for (var i = 0; i < data[0].length; ++i) { 183 | ret.push([]); 184 | } 185 | 186 | for (var i = 0; i < data.length; ++i) { 187 | for (var j = 0; j < data[i].length; ++j) { 188 | ret[j][i] = data[i][j]; 189 | } 190 | } 191 | 192 | return ret; 193 | } 194 | 195 | // getColumnsData iterates column by column in the input range and returns an array of objects. 196 | // Each object contains all the data for a given column, indexed by its normalized row name. 197 | // Arguments: 198 | // - sheet: the sheet object that contains the data to be processed 199 | // - range: the exact range of cells where the data is stored 200 | // - rowHeadersColumnIndex: specifies the column number where the row names are stored. 201 | // This argument is optional and it defaults to the column immediately left of the range; 202 | // Returns an Array of objects. 203 | function getColumnsData(sheet, range, rowHeadersColumnIndex) { 204 | rowHeadersColumnIndex = rowHeadersColumnIndex || range.getColumnIndex() - 1; 205 | var headersTmp = sheet.getRange(range.getRow(), rowHeadersColumnIndex, range.getNumRows(), 1).getValues(); 206 | var headers = normalizeHeaders(arrayTranspose(headersTmp)[0]); 207 | return getObjects(arrayTranspose(range.getValues()), headers); 208 | } -------------------------------------------------------------------------------- /ContentService/Simple-JSONP/SimpleHtml-JSFiddle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 |

welcome

16 | 17 | 18 | -------------------------------------------------------------------------------- /ContentService/Simple-JSONP/code.gs: -------------------------------------------------------------------------------- 1 | function doGet(request) { 2 | var result = 'hello world'; 3 | var content = request.parameters.prefix + '(' +JSON.stringify(result) + ')'; 4 | return ContentService.createTextOutput(content) 5 | .setMimeType(ContentService.MimeType.JSON); 6 | } 7 | -------------------------------------------------------------------------------- /ContentService/readme.md: -------------------------------------------------------------------------------- 1 | Click the image below for video with more details - 2 | 3 | [![ScreenShot](https://i.ytimg.com/vi/JRGzVdliQOQ/hqdefault.jpg)](http://www.youtube.com/watch?v=JRGzVdliQOQ&list=PL68F511F6E3C122EB) -------------------------------------------------------------------------------- /DriveSDK/AppsScript.gs: -------------------------------------------------------------------------------- 1 | //look for the first mtg right now in the user's calendar from now till next 2 hours 2 | function createMeetingNotes() { 3 | var lowerBound = new Date(); 4 | var upperBound = new Date(); 5 | upperBound.setHours(lowerBound.getHours()+2); 6 | 7 | var appts = CalendarApp.getEvents(lowerBound, upperBound); 8 | if(!appts || appts.length ==0){ 9 | Logger.log('No current appointments!'); 10 | return ''; 11 | } 12 | 13 | //naive implementation for demo - ideally we'll provide a picker if double booked 14 | var appt = appts[0]; 15 | var doc = DocumentApp.create('Meeting notes for ' + appt.getTitle()); 16 | doc.appendParagraph('Time: ' + appt.getStartTime()); 17 | doc.appendParagraph('Location: ' + appt.getLocation()); 18 | doc.appendParagraph('Attendees: ' + getEmails(appt)); 19 | doc.appendHorizontalRule(); 20 | doc.appendParagraph(appt.getDescription()); 21 | return doc.getUrl(); 22 | } 23 | 24 | function getEmails(appt){ 25 | var guests = appt.getGuestList(true); 26 | var emails = ''; 27 | for(var i=0;i

Email sent. Check your inbox!

"); 6 | } 7 | 8 | function zipAndSend(fileIds,emailAddress){ 9 | var names = {}; 10 | var zipFile = Utilities.zip(fileIds.map(function(i){ 11 | var f = DocsList.getFileById(i); 12 | Logger.log(f.getName()); 13 | var n = f.getName() + '.pdf'; 14 | while (names[n]) { n = '_' + n } 15 | names[n] = true; 16 | return f.getAs('application/pdf').setName(n); 17 | }), 'FilesForYou.zip') 18 | MailApp.sendEmail(emailAddress, 'Files you requested', 'Attached is the ZIP of the pdf documents we discussed', {attachments: [zipFile]}); 19 | } 20 | -------------------------------------------------------------------------------- /DriveSDK/FlowManager.gs: -------------------------------------------------------------------------------- 1 | var AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth'; 2 | var TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'; 3 | 4 | var REDIRECT_URL= 'YOUR_REDIRECT_URL';//ScriptApp.getService().getUrl(); 5 | var tokenPropertyName = 'GOOGLE_OAUTH_TOKEN'; 6 | 7 | var CLIENT_ID = 'YOUR_CLIENT_ID'; 8 | var CLIENT_SECRET = 'YOUR_CLIENT_SECRET'; 9 | 10 | function doGet(e) { 11 | var HTMLToOutput; 12 | 13 | if(e.parameters.state){ 14 | var state = JSON.parse(e.parameters.state); 15 | if(state.action === 'create'){ 16 | var meetingURL = createMeetingNotes(); 17 | HTMLToOutput = "

Meeting notes document created!

click here to open"; 18 | } 19 | else { 20 | zipAndSend(state.exportIds,Session.getEffectiveUser().getEmail()); 21 | HTMLToOutput = "

Email sent. Check your inbox!

"; 22 | } 23 | } 24 | else if(e.parameters.code){//if we get "code" as a parameter in, then this is a callback. we can make this more explicit 25 | getAndStoreAccessToken(e.parameters.code); 26 | HTMLToOutput = '

App is installed, you can close this window now or navigate to your Google Drive.

'; 27 | } 28 | else {//we are starting from scratch or resetting 29 | HTMLToOutput = "

Install this App into your Google Drive!

click here to start"; 30 | } 31 | return HtmlService.createHtmlOutput(HTMLToOutput); 32 | } 33 | 34 | function getURLForAuthorization(){ 35 | return AUTHORIZE_URL + '?response_type=code&client_id='+CLIENT_ID+'&redirect_uri='+REDIRECT_URL + 36 | '&scope=https://www.googleapis.com/auth/drive.install https://www.googleapis.com/auth/userinfo.email'; 37 | } 38 | 39 | function getAndStoreAccessToken(code){ 40 | var parameters = { method : 'post', 41 | payload : 'client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&grant_type=authorization_code&redirect_uri='+REDIRECT_URL+'&code=' + code}; 42 | 43 | var response = UrlFetchApp.fetch(TOKEN_URL,parameters).getContentText(); 44 | var tokenResponse = JSON.parse(response); 45 | UserProperties.setProperty(tokenPropertyName, tokenResponse.access_token); 46 | } 47 | 48 | function getUrlFetchOptions() { 49 | return {'contentType' : 'application/json', 50 | 'headers' : {'Authorization' : 'Bearer ' + UserProperties.getProperty(tokenPropertyName), 51 | 'Accept' : 'application/json'}}; 52 | } 53 | 54 | //naive check, not using for now, use refresh tokens and add proper checking 55 | function isTokenValid() { 56 | return UserProperties.getProperty(tokenPropertyName); 57 | } 58 | 59 | -------------------------------------------------------------------------------- /DriveSDK/readme.md: -------------------------------------------------------------------------------- 1 | Click the image below for video with more details - 2 | 3 | [![ScreenShot](https://i.ytimg.com/vi/R71oo-5NmPE/hqdefault.jpg)](http://www.youtube.com/watch?v=R71oo-5NmPE&list=PL68F511F6E3C122EB) -------------------------------------------------------------------------------- /FusionTables/RowUtilities.gs: -------------------------------------------------------------------------------- 1 | // getRowsData iterates row by row in the input range and returns an array of objects. 2 | // Each object contains all the data for a given row, indexed by its normalized column name. 3 | // Arguments: 4 | // - sheet: the sheet object that contains the data to be processed 5 | // - range: the exact range of cells where the data is stored 6 | // - columnHeadersRowIndex: specifies the row number where the column names are stored. 7 | // This argument is optional and it defaults to the row immediately above range; 8 | // Returns an Array of objects. 9 | function getRowsData(sheet, range, columnHeadersRowIndex) { 10 | columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1; 11 | var numColumns = range.getEndColumn() - range.getColumn() + 1; 12 | var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns); 13 | var headers = headersRange.getValues()[0]; 14 | return getObjects(range.getValues(), normalizeHeaders(headers)); 15 | } 16 | 17 | // getColumnsData iterates column by column in the input range and returns an array of objects. 18 | // Each object contains all the data for a given column, indexed by its normalized row name. 19 | // Arguments: 20 | // - sheet: the sheet object that contains the data to be processed 21 | // - range: the exact range of cells where the data is stored 22 | // - rowHeadersColumnIndex: specifies the column number where the row names are stored. 23 | // This argument is optional and it defaults to the column immediately left of the range; 24 | // Returns an Array of objects. 25 | function getColumnsData(sheet, range, rowHeadersColumnIndex) { 26 | rowHeadersColumnIndex = rowHeadersColumnIndex || range.getColumnIndex() - 1; 27 | var headersTmp = sheet.getRange(range.getRow(), rowHeadersColumnIndex, range.getNumRows(), 1).getValues(); 28 | var headers = normalizeHeaders(arrayTranspose(headersTmp)[0]); 29 | return getObjects(arrayTranspose(range.getValues()), headers); 30 | } 31 | 32 | 33 | // For every row of data in data, generates an object that contains the data. Names of 34 | // object fields are defined in keys. 35 | // Arguments: 36 | // - data: JavaScript 2d array 37 | // - keys: Array of Strings that define the property names for the objects to create 38 | function getObjects(data, keys) { 39 | var objects = []; 40 | for (var i = 0; i < data.length; ++i) { 41 | var object = {}; 42 | var hasData = false; 43 | for (var j = 0; j < data[i].length; ++j) { 44 | var cellData = data[i][j]; 45 | if (isCellEmpty(cellData)) { 46 | continue; 47 | } 48 | object[keys[j]] = cellData; 49 | hasData = true; 50 | } 51 | if (hasData) { 52 | objects.push(object); 53 | } 54 | } 55 | return objects; 56 | } 57 | 58 | // Returns an Array of normalized Strings. 59 | // Arguments: 60 | // - headers: Array of Strings to normalize 61 | function normalizeHeaders(headers) { 62 | var keys = []; 63 | for (var i = 0; i < headers.length; ++i) { 64 | var key = normalizeHeader(headers[i]); 65 | if (key.length > 0) { 66 | keys.push(key); 67 | } 68 | } 69 | return keys; 70 | } 71 | 72 | // Normalizes a string, by removing all alphanumeric characters and using mixed case 73 | // to separate words. The output will always start with a lower case letter. 74 | // This function is designed to produce JavaScript object property names. 75 | // Arguments: 76 | // - header: string to normalize 77 | // Examples: 78 | // "First Name" -> "firstName" 79 | // "Market Cap (millions) -> "marketCapMillions 80 | // "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored" 81 | function normalizeHeader(header) { 82 | var key = ""; 83 | var upperCase = false; 84 | for (var i = 0; i < header.length; ++i) { 85 | var letter = header[i]; 86 | if (letter == " " && key.length > 0) { 87 | upperCase = true; 88 | continue; 89 | } 90 | if (!isAlnum(letter)) { 91 | continue; 92 | } 93 | if (key.length == 0 && isDigit(letter)) { 94 | continue; // first character must be a letter 95 | } 96 | if (upperCase) { 97 | upperCase = false; 98 | key += letter.toUpperCase(); 99 | } else { 100 | key += letter.toLowerCase(); 101 | } 102 | } 103 | return key; 104 | } 105 | 106 | // Returns true if the cell where cellData was read from is empty. 107 | // Arguments: 108 | // - cellData: string 109 | function isCellEmpty(cellData) { 110 | return typeof(cellData) == "string" && cellData == ""; 111 | } 112 | 113 | // Returns true if the character char is alphabetical, false otherwise. 114 | function isAlnum(char) { 115 | return char >= 'A' && char <= 'Z' || 116 | char >= 'a' && char <= 'z' || 117 | isDigit(char); 118 | } 119 | 120 | // Returns true if the character char is a digit, false otherwise. 121 | function isDigit(char) { 122 | return char >= '0' && char <= '9'; 123 | } 124 | 125 | // Given a JavaScript 2d Array, this function returns the transposed table. 126 | // Arguments: 127 | // - data: JavaScript 2d Array 128 | // Returns a JavaScript 2d Array 129 | // Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]]. 130 | function arrayTranspose(data) { 131 | if (data.length == 0 || data[0].length == 0) { 132 | return null; 133 | } 134 | 135 | var ret = []; 136 | for (var i = 0; i < data[0].length; ++i) { 137 | ret.push([]); 138 | } 139 | 140 | for (var i = 0; i < data.length; ++i) { 141 | for (var j = 0; j < data[i].length; ++j) { 142 | ret[j][i] = data[i][j]; 143 | } 144 | } 145 | 146 | return ret; 147 | } -------------------------------------------------------------------------------- /FusionTables/SampleDataExchange.gs: -------------------------------------------------------------------------------- 1 | function onInstall(){ 2 | onOpen(); 3 | } 4 | 5 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 6 | 7 | function onOpen() { 8 | var menuEntries = [{name: "Login", functionName: "login"}, 9 | {name: "Download from Fusion Tables", functionName: "downloadFromFusionTables"}, 10 | {name: "Upload to Fusion Tables", functionName: "uploadToFusionTables"}, 11 | {name: "Reset credentials", functionName: "clearCreds"}]; 12 | ss.addMenu("Fusion Tables", menuEntries); 13 | } 14 | 15 | function login(){ 16 | if(isTokenValid()){ 17 | HTMLToOutput = '

Already have token

'; 18 | } 19 | else {//we are starting from scratch or resetting 20 | HTMLToOutput = "

You need to login

click here to start
Re-open this window when you return."; 21 | } 22 | ss.show(HtmlService.createHtmlOutput(HTMLToOutput)); 23 | } 24 | 25 | function doGet(e) { 26 | var HTMLToOutput; 27 | if(e.parameters.code){//if we get "code" as a parameter in, then this is a callback. we can make this more explicit 28 | getAndStoreAccessToken(e.parameters.code); 29 | HTMLToOutput = '

Finished with oAuth

You can close this window.'; 30 | } 31 | return HtmlService.createHtmlOutput(HTMLToOutput); 32 | } 33 | var tableName = '1ETYzpkpTm4SfOMFTWHYgT9q1s4mSIE87yQCnQ04'; 34 | 35 | function downloadFromFusionTables(){ 36 | var schoolName = lookupSchoolName(); 37 | 38 | var dataResponse = runSQL("select ROWID,StudentID,Subject,Grade,School from "+tableName+" where School = '"+schoolName+"'"); 39 | var respObject = JSON.parse(dataResponse); 40 | ss.appendRow(respObject.columns); 41 | for(var i in respObject.rows){ 42 | ss.appendRow(respObject.rows[i]); 43 | } 44 | ss.getActiveSheet().clearNotes(); 45 | } 46 | 47 | function runSQL(sql){ 48 | var getDataURL = 'https://www.googleapis.com/fusiontables/v1/query?sql='+sql; 49 | var dataResponse = UrlFetchApp.fetch(getDataURL,getUrlFetchOptions()).getContentText(); 50 | return dataResponse; 51 | } 52 | 53 | var tempEditedProperty = 'CURRENT_EDITED'; 54 | 55 | //simple tracker to see what changed. need to add more validation logic and safety checks here. 56 | function onEdit(e){ 57 | e.range.setComment("Edited at: " + new Date().toTimeString()); 58 | var rowid = ss.getActiveSheet().getRange(e.range.getRow(),1).getValue(); 59 | var currentEditedItems = UserProperties.getProperty(tempEditedProperty) || ''; 60 | currentEditedItems += rowid+','; 61 | UserProperties.setProperty(tempEditedProperty,currentEditedItems); 62 | } 63 | 64 | function uploadToFusionTables() { 65 | var currentEditedItems = UserProperties.getProperty(tempEditedProperty) || ''; 66 | var rowIdsEdited = currentEditedItems.split(','); 67 | var rowObjects = getRowsData(ss.getActiveSheet(), ss.getDataRange(),1); 68 | var runningLog = ''; 69 | for(var i = 1;i-1){ 71 | runningLog += 'updating rowid = ' + rowObjects[i].rowid + ' with grade of ' + rowObjects[i].grade + '. '; 72 | runSQL("update "+tableName+" SET Grade = "+rowObjects[i].grade+" WHERE ROWID = '"+String(rowObjects[i].rowid)+"'"); 73 | } 74 | } 75 | UserProperties.deleteProperty(tempEditedProperty); 76 | ss.getActiveSheet().clearNotes(); 77 | if(runningLog){ 78 | Browser.msgBox(runningLog); 79 | } 80 | } 81 | 82 | var AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth'; //step 1. we can actually start directly here if that is necessary 83 | var TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'; //step 2. after we get the callback, go get token 84 | 85 | 86 | //PUT YOUR OWN SETTINGS HERE 87 | var CLIENT_ID = 'YOUR_CLIENT_ID'; 88 | var CLIENT_SECRET='YOUR_CLIENT_SECRET'; 89 | var REDIRECT_URL= 'YOUR_REDIRECT_URL'; 90 | 91 | var tokenPropertyName = 'FUSIONTABLES_OAUTH_TOKEN'; 92 | 93 | function getURLForAuthorization(){ 94 | return AUTHORIZE_URL + '?response_type=code&client_id='+CLIENT_ID+'&redirect_uri='+REDIRECT_URL + 95 | '&scope=https://www.googleapis.com/auth/fusiontables&state=/profile'; 96 | } 97 | 98 | function getAndStoreAccessToken(code){ 99 | var parameters = { 100 | method : 'post', 101 | payload : 'client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&grant_type=authorization_code&redirect_uri='+REDIRECT_URL+'&code=' + code 102 | }; 103 | 104 | var response = UrlFetchApp.fetch(TOKEN_URL,parameters).getContentText(); 105 | var tokenResponse = JSON.parse(response); 106 | 107 | //store the token for later retrival 108 | UserProperties.setProperty(tokenPropertyName, tokenResponse.access_token); 109 | } 110 | 111 | function getUrlFetchOptions() { 112 | var token = UserProperties.getProperty(tokenPropertyName); 113 | return { 114 | "method" : "post", 115 | "contentType" : "application/json", 116 | "headers" : { 117 | "Authorization" : "Bearer " + token, 118 | "Accept" : "application/json" 119 | } 120 | }; 121 | } 122 | 123 | // we don't have a logout option here. for now, manually clear out the token under File->Project->User Properties 124 | function isTokenValid() { 125 | var token = UserProperties.getProperty(tokenPropertyName); 126 | if(!token){ //if its empty or undefined 127 | return false; 128 | } 129 | return true; //naive check 130 | } 131 | 132 | 133 | function clearCreds(){ 134 | UserProperties.deleteProperty(tokenPropertyName); 135 | Browser.msgBox('Tokens cleared. Please login again from the menu before using Fusion tables'); 136 | } 137 | 138 | 139 | var schoolName = { 140 | "tom.principal@acmemiddleschool.edu":'Acme Middle School', 141 | "arun.at.pyxis@gmail.com":'Short Hills Prep', 142 | "arun.appsscript@gmail.com":'PK Middle School' 143 | } 144 | var user = Session.getEffectiveUser().getEmail(); 145 | 146 | function lookupSchoolName(){ 147 | return schoolName[user] || schoolName[0]; 148 | } 149 | 150 | -------------------------------------------------------------------------------- /FusionTables/readme.md: -------------------------------------------------------------------------------- 1 | Click the image below for video with more details - 2 | 3 | [![ScreenShot](https://i.ytimg.com/vi/eHM0zbUE5A0/hqdefault.jpg)](http://www.youtube.com/watch?v=eHM0zbUE5A0&list=PL68F511F6E3C122EB) -------------------------------------------------------------------------------- /GoogleAppsAdminAudit.gs: -------------------------------------------------------------------------------- 1 | function doGet(e) { 2 | var HTMLToOutput; 3 | if(e.parameters.code){//if we get "code" as a parameter in, then this is a callback. we can make this more explicit 4 | getAndStoreAccessToken(e.parameters.code); 5 | HTMLToOutput = '

Finished with oAuth

'; 6 | } 7 | else if(isTokenValid()){//if we already have a valid token, go off and start working with data 8 | HTMLToOutput = '

Already have token

'; 9 | } 10 | else {//we are starting from scratch or resetting 11 | return HtmlService.createHtmlOutput("

Lets start with oAuth

click here to start"); 12 | } 13 | 14 | HTMLToOutput += getData(); 15 | return HtmlService.createHtmlOutput(HTMLToOutput); 16 | } 17 | 18 | //see docs here - https://developers.google.com/google-apps/admin-audit/ 19 | function getData(){ 20 | var getCustomerIdUrl = 'https://apps-apis.google.com/a/feeds/customer/2.0/customerId?alt=json'; 21 | var getCustomeerIdData = UrlFetchApp.fetch(getCustomerIdUrl,getUrlFetchOptions()).getContentText(); 22 | 23 | var responseObject = JSON.parse(getCustomeerIdData); 24 | 25 | //customerId is the 2nd item. make this more robust! 26 | var customerId = responseObject.entry.apps$property[1].value; 27 | 28 | //cpanel app ID is always the same. 29 | var activityUrl = 'https://www.googleapis.com/apps/reporting/audit/v1/'+customerId+'/207535951991'; 30 | var dataResponse = UrlFetchApp.fetch(activityUrl,getUrlFetchOptions()).getContentText(); 31 | 32 | var dataObject = JSON.parse(dataResponse); 33 | var ss = SpreadsheetApp.create('Audit Log'); 34 | for(var i = 0;iOpen Spreadsheet here"; 53 | } 54 | 55 | //hardcoded here for easily tweaking this. should move this to ScriptProperties or better parameterize them 56 | var AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth'; //step 1. we can actually start directly here if that is necessary 57 | var TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'; //step 2. after we get the callback, go get token 58 | 59 | var CLIENT_ID = 'YOUR_CLIENT_ID'; 60 | var CLIENT_SECRET = 'YOUR_CLIENT_SECRET'; 61 | 62 | var REDIRECT_URL= 'YOUR_DEPLOYED_URL';//safer than ScriptApp.getService().getUrl() for domain accounts 63 | 64 | //this is the user propety where we'll store the token, make sure this is unique across all user properties across all scripts 65 | var tokenPropertyName = 'GOOGLE_OAUTH_TOKEN'; 66 | var baseURLPropertyName = 'GOOGLE_INSTANCE_URL'; 67 | 68 | function getURLForAuthorization(){ 69 | return AUTHORIZE_URL + '?response_type=code&client_id='+CLIENT_ID+'&redirect_uri='+REDIRECT_URL + 70 | '&scope=https://apps-apis.google.com/a/feeds/policies/ https://www.googleapis.com/auth/apps/reporting/audit.readonly&state=/profile'; 71 | } 72 | 73 | //Google requires POST, salesforce and slc worked with GET 74 | function getAndStoreAccessToken(code){ 75 | var parameters = { 76 | method : 'post', 77 | payload : 'client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&grant_type=authorization_code&redirect_uri='+REDIRECT_URL+'&code=' + code 78 | }; 79 | 80 | var response = UrlFetchApp.fetch(TOKEN_URL,parameters).getContentText(); 81 | var tokenResponse = JSON.parse(response); 82 | 83 | //store the token for later retrieval 84 | UserProperties.setProperty(tokenPropertyName, tokenResponse.access_token); 85 | } 86 | 87 | function getUrlFetchOptions() { 88 | var token = UserProperties.getProperty(tokenPropertyName); 89 | return {"contentType" : "application/json", 90 | "headers" : {"Authorization" : "Bearer " + token, 91 | "Accept" : "application/json"} 92 | }; 93 | } 94 | 95 | //naive check, add checking freshness or refreshing oauth tokens. also needs logout 96 | function isTokenValid() { 97 | var token = UserProperties.getProperty(tokenPropertyName); 98 | if(!token){ //if its empty or undefined 99 | return false; 100 | } 101 | return true; 102 | } -------------------------------------------------------------------------------- /IO2013/Drive/Code.gs: -------------------------------------------------------------------------------- 1 | function doGet(e) { 2 | var HTMLToOutput; 3 | if(e.parameter.state){ 4 | var state = JSON.parse(e.parameter.state); 5 | if(state.action === 'create'){ 6 | var newFolder = DriveApp.createFolder('New Assignment'); 7 | 8 | var templateQuiz = DriveApp.getFileById(QUIZ_TEMPLATE); 9 | var templateRoster = DriveApp.getFileById(ROSTER_TEMPLATE); 10 | 11 | var newQuiz = templateQuiz.makeCopy('Quiz Questions'); 12 | newFolder.addFile(newQuiz); 13 | DriveApp.getRootFolder().removeFile(newQuiz); 14 | 15 | var newRoster = templateRoster.makeCopy('Roster'); 16 | newFolder.addFile(newRoster); 17 | DriveApp.getRootFolder().removeFile(newRoster); 18 | 19 | var objectToSaveForClose = { type: 'close', 20 | rosterID : newRoster.getId()} 21 | 22 | var objectToSaveForPublish = { type: 'publish', 23 | assignmentFolderID : newFolder.getId(), 24 | rosterID : newRoster.getId(), 25 | quizID : newQuiz.getId()} 26 | 27 | var closeBlob = Utilities.newBlob(JSON.stringify(objectToSaveForClose), 'application/drive-assignment-creator', 'Close Quiz'); 28 | var publishBlob = Utilities.newBlob(JSON.stringify(objectToSaveForPublish), 'application/drive-assignment-creator', 'Publish Quiz'); 29 | newFolder.createFile(closeBlob); 30 | newFolder.createFile(publishBlob); 31 | 32 | HTMLToOutput = 'Assignment folder created with necessary template. Please update the Roster, fill in the quiz questions and click on the Publish icon in the folder. '; 33 | } else { 34 | var fileID = state.ids[0]; 35 | var content = DriveApp.getFileById(fileID).getBlob().getDataAsString(); 36 | var contentObject = JSON.parse(content); 37 | var ssID = contentObject.rosterID; 38 | var ss = SpreadsheetApp.openById(ssID); 39 | var sheet = ss.getSheets()[0]; 40 | var rosterRange = ss.getRangeByName('RosterRange'); 41 | var rosterObjects = getRowsData(sheet, rosterRange); 42 | 43 | 44 | if(contentObject.type === 'close'){ 45 | for(var i in rosterObjects){ 46 | var rosterItem = rosterObjects[i]; 47 | var quickCopy = DriveApp.getFileById(rosterItem.fileid); 48 | quickCopy.removeEditor(rosterItem.studentEmailAddress); 49 | quickCopy.addViewer(rosterItem.studentEmailAddress); 50 | } 51 | HTMLToOutput = 'Quiz is now closed. Your students will no longer be able to save any changes. They wil be able to view your notes on their quizes. '; 52 | 53 | }else if(contentObject.type === 'publish'){ 54 | var quizFileID = contentObject.quizID; 55 | var quiz = DriveApp.getFileById(quizFileID); 56 | var folderID = contentObject.assignmentFolderID; 57 | var folder = DriveApp.getFolderById(folderID); 58 | 59 | for(var i in rosterObjects){ 60 | var rosterItem = rosterObjects[i]; 61 | var quizCopy = quiz.makeCopy('Quiz - ' + rosterItem.studentName); 62 | folder.addFile(quizCopy); 63 | DriveApp.getRootFolder().removeFile(quizCopy); 64 | quizCopy.addEditor(rosterItem.studentEmailAddress); 65 | rosterItem.fileid = quizCopy.getId(); 66 | } 67 | setRowsData(sheet,rosterObjects); 68 | HTMLToOutput = 'Completed publishing your quizes. Your students should be able to see the files in their "Shared with me" view.'; 69 | } 70 | 71 | } 72 | 73 | } 74 | else if(e.parameter.code){//if we get "code" as a parameter in, then this is a callback. we can make this more explicit 75 | getAndStoreAccessToken(e.parameter.code); 76 | HTMLToOutput = 'App is installed, you can close this window now or open up Google Drive.'; 77 | } 78 | else {//we are starting from scratch or resetting 79 | } 80 | var t = HtmlService.createTemplateFromFile('ui') 81 | t.message = HTMLToOutput; 82 | return t.evaluate().setSandboxMode(HtmlService.SandboxMode.NATIVE) 83 | } 84 | 85 | function getURLForAuthorization(){ 86 | return AUTHORIZE_URL + '?response_type=code&client_id='+CLIENT_ID+'&redirect_uri='+REDIRECT_URL +'&scope=https://www.googleapis.com/auth/drive.install'; 87 | } 88 | 89 | function getAndStoreAccessToken(code){ 90 | var parameters = { method : 'post', 91 | payload : 'client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&grant_type=authorization_code&redirect_uri='+REDIRECT_URL+'&code=' + code}; 92 | //no need to do anything with the token going forward. 93 | var response = UrlFetchApp.fetch(TOKEN_URL,parameters).getContentText(); 94 | } 95 | 96 | 97 | //replace with any template files you want - 98 | var ROSTER_TEMPLATE = '0AkJNj_IM2wiPdEZzbFUxMjQteGtQS1JadHg5VmFMdGc'; 99 | var QUIZ_TEMPLATE = '1_LIZp1lahFhNouXsZNI1s6UluBskt_LOwQGlJIj2ftY'; 100 | 101 | var AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth'; 102 | var TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'; 103 | 104 | //put your URL below or use ScriptApp.getService().getUrl(); 105 | var REDIRECT_URL= 'https://script.google.com/macros/s/AKfycbxK6aULLp8CL47aiUM_tHZCX9-2YNOK0yrp-ujnUQV8CrUQkUGk/exec'; 106 | var CLIENT_ID = ScriptProperties.getProperty('CLIENT_ID') 107 | var CLIENT_SECRET = ScriptProperties.getProperty('CLIENT_SECRET'); 108 | 109 | -------------------------------------------------------------------------------- /IO2013/Drive/README.md: -------------------------------------------------------------------------------- 1 | Companion code for Google IO 2013 session - Integrate Google Drive with Google Apps Script 2 | 3 | https://developers.google.com/events/io/sessions/325412094 4 | 5 | For setup - 6 | 7 | You'll need to create OAuth 2 client ID/secret at http://developers.google.com/console and store it in the right ScriptProperties. 8 | -------------------------------------------------------------------------------- /IO2013/Drive/SpreadsheetUtils.gs: -------------------------------------------------------------------------------- 1 | //https://developers.google.com/apps-script/storing_data_spreadsheets#reading 2 | 3 | 4 | // getRowsData iterates row by row in the input range and returns an array of objects. 5 | // Each object contains all the data for a given row, indexed by its normalized column name. 6 | // Arguments: 7 | // - sheet: the sheet object that contains the data to be processed 8 | // - range: the exact range of cells where the data is stored 9 | // - columnHeadersRowIndex: specifies the row number where the column names are stored. 10 | // This argument is optional and it defaults to the row immediately above range; 11 | // Returns an Array of objects. 12 | function getRowsData(sheet, range, columnHeadersRowIndex) { 13 | columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1; 14 | var numColumns = range.getLastColumn() - range.getColumn() + 1; 15 | var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns); 16 | var headers = headersRange.getValues()[0]; 17 | return getObjects(range.getValues(), normalizeHeaders(headers)); 18 | } 19 | 20 | // setRowsData fills in one row of data per object defined in the objects Array. 21 | // For every Column, it checks if data objects define a value for it. 22 | // Arguments: 23 | // - sheet: the Sheet Object where the data will be written 24 | // - objects: an Array of Objects, each of which contains data for a row 25 | // - optHeadersRange: a Range of cells where the column headers are defined. This 26 | // defaults to the entire first row in sheet. 27 | // - optFirstDataRowIndex: index of the first row where data should be written. This 28 | // defaults to the row immediately below the headers. 29 | function setRowsData(sheet, objects, optHeadersRange, optFirstDataRowIndex) { 30 | var headersRange = optHeadersRange || sheet.getRange(1, 1, 1, sheet.getMaxColumns()); 31 | var firstDataRowIndex = optFirstDataRowIndex || headersRange.getRowIndex() + 1; 32 | var headers = normalizeHeaders(headersRange.getValues()[0]); 33 | 34 | var data = []; 35 | for (var i = 0; i < objects.length; ++i) { 36 | var values = [] 37 | for (j = 0; j < headers.length; ++j) { 38 | var header = headers[j]; 39 | // If the header is non-empty and the object value is 0... 40 | if ((header.length > 0) && (objects[i][header] == 0)) { 41 | values.push(0); 42 | } 43 | // If the header is empty or the object value is empty... 44 | else if ((!(header.length > 0)) || (objects[i][header]=='')) { 45 | values.push(''); 46 | } 47 | else { 48 | values.push(objects[i][header]); 49 | } 50 | } 51 | data.push(values); 52 | } 53 | 54 | var destinationRange = sheet.getRange(firstDataRowIndex, headersRange.getColumnIndex(), 55 | objects.length, headers.length); 56 | destinationRange.setValues(data); 57 | } 58 | 59 | // getColumnsData iterates column by column in the input range and returns an array of objects. 60 | // Each object contains all the data for a given column, indexed by its normalized row name. 61 | // Arguments: 62 | // - sheet: the sheet object that contains the data to be processed 63 | // - range: the exact range of cells where the data is stored 64 | // - rowHeadersColumnIndex: specifies the column number where the row names are stored. 65 | // This argument is optional and it defaults to the column immediately left of the range; 66 | // Returns an Array of objects. 67 | function getColumnsData(sheet, range, rowHeadersColumnIndex) { 68 | rowHeadersColumnIndex = rowHeadersColumnIndex || range.getColumnIndex() - 1; 69 | var headersTmp = sheet.getRange(range.getRow(), rowHeadersColumnIndex, range.getNumRows(), 1).getValues(); 70 | var headers = normalizeHeaders(arrayTranspose(headersTmp)[0]); 71 | return getObjects(arrayTranspose(range.getValues()), headers); 72 | } 73 | 74 | 75 | // For every row of data in data, generates an object that contains the data. Names of 76 | // object fields are defined in keys. 77 | // Arguments: 78 | // - data: JavaScript 2d array 79 | // - keys: Array of Strings that define the property names for the objects to create 80 | function getObjects(data, keys) { 81 | var objects = []; 82 | for (var i = 0; i < data.length; ++i) { 83 | var object = {}; 84 | var hasData = false; 85 | for (var j = 0; j < data[i].length; ++j) { 86 | var cellData = data[i][j]; 87 | if (isCellEmpty(cellData)) { 88 | continue; 89 | } 90 | object[keys[j]] = cellData; 91 | hasData = true; 92 | } 93 | if (hasData) { 94 | objects.push(object); 95 | } 96 | } 97 | return objects; 98 | } 99 | 100 | // Returns an Array of normalized Strings. 101 | // Arguments: 102 | // - headers: Array of Strings to normalize 103 | function normalizeHeaders(headers) { 104 | var keys = []; 105 | for (var i = 0; i < headers.length; ++i) { 106 | var key = normalizeHeader(headers[i]); 107 | if (key.length > 0) { 108 | keys.push(key); 109 | } 110 | } 111 | return keys; 112 | } 113 | 114 | // Normalizes a string, by removing all alphanumeric characters and using mixed case 115 | // to separate words. The output will always start with a lower case letter. 116 | // This function is designed to produce JavaScript object property names. 117 | // Arguments: 118 | // - header: string to normalize 119 | // Examples: 120 | // "First Name" -> "firstName" 121 | // "Market Cap (millions) -> "marketCapMillions 122 | // "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored" 123 | function normalizeHeader(header) { 124 | var key = ""; 125 | var upperCase = false; 126 | for (var i = 0; i < header.length; ++i) { 127 | var letter = header[i]; 128 | if (letter == " " && key.length > 0) { 129 | upperCase = true; 130 | continue; 131 | } 132 | if (!isAlnum(letter)) { 133 | continue; 134 | } 135 | if (key.length == 0 && isDigit(letter)) { 136 | continue; // first character must be a letter 137 | } 138 | if (upperCase) { 139 | upperCase = false; 140 | key += letter.toUpperCase(); 141 | } else { 142 | key += letter.toLowerCase(); 143 | } 144 | } 145 | return key; 146 | } 147 | 148 | // Returns true if the cell where cellData was read from is empty. 149 | // Arguments: 150 | // - cellData: string 151 | function isCellEmpty(cellData) { 152 | return typeof(cellData) == "string" && cellData == ""; 153 | } 154 | 155 | // Returns true if the character char is alphabetical, false otherwise. 156 | function isAlnum(char) { 157 | return char >= 'A' && char <= 'Z' || 158 | char >= 'a' && char <= 'z' || 159 | isDigit(char); 160 | } 161 | 162 | // Returns true if the character char is a digit, false otherwise. 163 | function isDigit(char) { 164 | return char >= '0' && char <= '9'; 165 | } 166 | 167 | // Given a JavaScript 2d Array, this function returns the transposed table. 168 | // Arguments: 169 | // - data: JavaScript 2d Array 170 | // Returns a JavaScript 2d Array 171 | // Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]]. 172 | function arrayTranspose(data) { 173 | if (data.length == 0 || data[0].length == 0) { 174 | return null; 175 | } 176 | 177 | var ret = []; 178 | for (var i = 0; i < data[0].length; ++i) { 179 | ret.push([]); 180 | } 181 | 182 | for (var i = 0; i < data.length; ++i) { 183 | for (var j = 0; j < data[i].length; ++j) { 184 | ret[j][i] = data[i][j]; 185 | } 186 | } 187 | 188 | return ret; 189 | } -------------------------------------------------------------------------------- /IO2013/Drive/ui.html: -------------------------------------------------------------------------------- 1 | 2 | Google Drive Quiz Manager 3 | 4 | 6 | 17 | 18 | 25 | 26 | 27 |
Google Drive Quiz Manager
28 | 29 | 30 |
31 |

32 |

Please click on the link below to install the Quiz Manager app to your Google Drive. You will be redirected to a standard Google authorization page.

33 |

34 |
35 | Install app!
36 |

37 |
38 | 39 |
40 |

41 |

42 |

43 |
44 | Close window
45 |

46 |
47 | 48 |

49 |

50 | 51 |

52 | 53 | 54 | -------------------------------------------------------------------------------- /IO2013/YouTubeAnalytics/Code.gs: -------------------------------------------------------------------------------- 1 | function onOpen(){ 2 | SpreadsheetApp.getActiveSpreadsheet() 3 | .addMenu('YouTube Analytics',[{name:'Ad hoc report', functionName:'showUI'}, 4 | {name: 'Twitter trending', functionName:'twitter'}]); 5 | } 6 | 7 | function showUI(){ 8 | var ui = HtmlService.createTemplateFromFile('ui').evaluate().setSandboxMode(HtmlService.SandboxMode.NATIVE).setHeight(350); 9 | SpreadsheetApp.getActiveSpreadsheet().show(ui); 10 | } 11 | 12 | function sendNightlyEmail(){ 13 | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Published Dashboard'); 14 | var chart = sheet.getCharts()[0]; 15 | MailApp.sendEmail(Session.getActiveUser().getEmail(), 'Daily YouTube Sharing report', 'See attached image', {name:'Sharing chart',attachments:[chart.getBlob()]}); 16 | } 17 | 18 | function alertOnViewsIncrease(){ 19 | //perform business logic here to compare against previously stored values/thresholds 20 | } 21 | 22 | function doGet(e) { 23 | var HTMLToOutput; 24 | if(e.parameter.code){//if we get "code" as a parameter in, then this is a callback. we can make this more explicit 25 | getAndStoreAccessToken(e.parameters.code); 26 | HTMLToOutput = HtmlService.createHtmlOutputFromFile('oauthsuccess').getContent(); 27 | } 28 | else if(isTokenValid()){//we already have a valid token but this should never start here. 29 | HTMLToOutput = '

Invalid access

'; 30 | } 31 | else {//we are starting from scratch but this should never start here. 32 | HTMLToOutput = "

Invalid access

"; 33 | } 34 | return HtmlService.createHtmlOutput(HTMLToOutput).setSandboxMode(HtmlService.SandboxMode.NATIVE); 35 | } 36 | 37 | 38 | function getData(startdate,enddate,metrics,dimensions){ 39 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 40 | //these are the channels and contentOwner settings we care for - 41 | var ids = 'contentOwner==promo-pso-brazil'; 42 | var filters = 'channel==UCEN58iXQg82TXgsDCjWqIkg'; 43 | 44 | var sheet = ss.insertSheet(); 45 | ss.setActiveSheet(sheet); 46 | var base_url = 'https://www.googleapis.com/youtube/analytics/v1/reports?'; 47 | var getDataURL = base_url + 'ids='+ids+'&start-date='+startdate+'&end-date='+enddate+'&metrics='+metrics+'&dimensions='+dimensions+'&filters='+filters; 48 | Logger.log(getDataURL); 49 | 50 | var dataResponse = UrlFetchApp.fetch(getDataURL,getUrlFetchOptions()).getContentText(); 51 | var dataObj = JSON.parse(dataResponse); 52 | var headers = []; 53 | for(var i = 0;dataObj.columnHeaders && i 2 | YouTube Analytics 3 | 4 | 5 | 6 | Ad hoc Report Generator 7 | 8 |

9 | Thank you for authorization the application. Please close this window and return to the spreadsheet. 10 |

11 | Close window 12 |

13 | -------------------------------------------------------------------------------- /IO2013/YouTubeAnalytics/twitter.gs: -------------------------------------------------------------------------------- 1 | function twitter(){ 2 | var oauthCfg = UrlFetchApp.addOAuthService('twitter'); 3 | oauthCfg.setAccessTokenUrl('https://api.twitter.com/oauth/access_token'); 4 | oauthCfg.setRequestTokenUrl('https://api.twitter.com/oauth/request_token'); 5 | oauthCfg.setAuthorizationUrl('https://api.twitter.com/oauth/authorize'); 6 | oauthCfg.setConsumerKey(ScriptProperties.getProperty('TWITTER_CLIENT_ID')); 7 | oauthCfg.setConsumerSecret(ScriptProperties.getProperty('TWITTER_CLIENT_SECRET')); 8 | var options = {oAuthServiceName:'twitter',oAuthUseToken:'always'}; 9 | 10 | var WOEIDs = {}; 11 | WOEIDs['United States'] = '23424977'; 12 | WOEIDs['Brazil'] = '23424768' 13 | WOEIDs['South Africa'] = '23424942' 14 | WOEIDs['United Kindom'] = '23424975'; 15 | WOEIDs['Canada'] = '23424775' 16 | 17 | var headers = []; 18 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 19 | 20 | for(var woe in WOEIDs){ 21 | var url = 'https://api.twitter.com/1.1/trends/place.json?id='+WOEIDs[woe]; //eg. all = 1, london = 44418 22 | var response = UrlFetchApp.fetch(url, options).getContentText(); 23 | 24 | var myObject = JSON.parse(response)[0]; 25 | var trends = [] 26 | for(var i = 0;myObject.trends && i 2 | YouTube Analytics 3 | 4 | 6 | 17 | 18 | 62 | 63 | 64 | Loading... 66 | 67 | Ad hoc Report Generator 68 | 69 | 70 |
71 |

72 | Please click on the link below to authorize application to the YouTube Analytics API. This page will automatically refresh when you return. 73 |

74 | Authorize app
75 |

76 |
77 | 78 | 79 | 80 |
81 | 82 |
83 | 84 |
85 |
86 |
87 | 88 |
89 |
90 |
91 | 92 |
98 |
99 |
100 | 101 |
107 |
108 | 109 |
110 | 111 | 112 |
113 |
114 | -------------------------------------------------------------------------------- /NYTimesCampaignContribution.gs: -------------------------------------------------------------------------------- 1 | function getData(){ 2 | var API_KEY = 'YOUR_API_KEY_HERE'; 3 | var url = 'http://api.nytimes.com/svc/elections/us/v3/finances/2012/president/states/NY.json?api-key='+API_KEY; 4 | 5 | var response = UrlFetchApp.fetch(url).getContentText(); 6 | var respObj = JSON.parse(response); 7 | 8 | var ss = SpreadsheetApp.create('NY Presidential Campaign') 9 | //add a simple header 10 | ss.appendRow(['Candidate','Number of Contributions','Total $ Contributions']); 11 | 12 | for(var i in respObj.results){ 13 | ss.appendRow([respObj.results[i].full_name,respObj.results[i].contribution_count,respObj.results[i].total]); 14 | } 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GoogleAppsScript 2 | ================ 3 | 4 | Sample code for Google Apps Script. 5 | These are not official Google samples. These are more of proof of concepts to support demos and other presentations. 6 | 7 | Please use this code as a guidline and not as production ready code. 8 | 9 | http://developers.google.com/apps-script 10 | 11 | http://script.google.com 12 | 13 | All code in this folder released under [Apache 2.0 license](http://www.apache.org/licenses/LICENSE-2.0). 14 | -------------------------------------------------------------------------------- /SAP/Forms/Code.gs: -------------------------------------------------------------------------------- 1 | function onFormSubmit(e) { 2 | var myObject = {}; 3 | var itemResponses = e.response.getItemResponses(); 4 | for (var j = 0; j < itemResponses.length; j++) { 5 | var itemResponse = itemResponses[j]; 6 | myObject[itemResponse.getItem().getTitle()] = itemResponse.getResponse(); 7 | //Logger.log('"%s" was "%s"',itemResponse.getItem().getTitle(), itemResponse.getResponse()); 8 | } 9 | 10 | Logger.log(myObject); 11 | 12 | var t = HtmlService.createTemplateFromFile('product_template'); 13 | t.data = myObject; 14 | var postPayload = t.evaluate().getContent(); 15 | 16 | Logger.log(postPayload); 17 | 18 | var base_url = "https://sapes1.sapdevcenter.com/sap/opu/odata/IWBEP/GWDEMO/ProductCollection"; 19 | var additionParams = "$format=json"; 20 | var user = 'YOUR_USERNAME'; 21 | var password = 'YOUR_PASSWORD'; 22 | var header = 'Basic '+Utilities.base64Encode(user+':'+password); 23 | 24 | var headers = { 25 | Authorization: header, 26 | 'X-CSRF-Token' : 'Fetch', 27 | "Content-Type": "application/atom+xml" 28 | } 29 | 30 | 31 | //Logger.log(headers); 32 | var response = UrlFetchApp.fetch(base_url,{headers: headers, muteHttpExceptions: true}); 33 | var csrf_token = response.getAllHeaders()['x-csrf-token']; 34 | var cookie = response.getAllHeaders()['set-cookie']; 35 | 36 | //Logger.log(response.getAllHeaders()); 37 | 38 | headers['X-CSRF-Token'] = csrf_token; 39 | headers['Cookie'] = cookie.join('; '); 40 | 41 | 42 | 43 | Logger.log(headers); 44 | 45 | //var deleteResponse = UrlFetchApp.fetch(id,{headers: headers, method : 'DELETE', muteHttpExceptions: true}); 46 | 47 | 48 | var createResponse = UrlFetchApp.fetch(base_url,{headers: headers, method : 'POST', contentType : "application/atom+xml", payload: postPayload, muteHttpExceptions: true}); 49 | 50 | Logger.log(createResponse.getContentText()); 51 | } 52 | -------------------------------------------------------------------------------- /SAP/Forms/product_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Regular VAT 6 | ARUN- 7 | MTR 8 | 500.00 9 | 4.200 10 | 0.320 11 | 0100000000 12 | 13 | Notebooks 14 | 1 15 | EA 16 | PR 17 | KGM 18 | SAP 19 | EUR 20 | 0.040 21 | 0.210 22 | 23 | 24 | -------------------------------------------------------------------------------- /SAP/Gmail Schemas/Code.gs: -------------------------------------------------------------------------------- 1 | function testSchemas() { 2 | var htmlBody = HtmlService.createHtmlOutputFromFile('mail_template').getContent(); 3 | 4 | GmailApp.sendEmail(Session.getEffectiveUser().getEmail(),'Alert for SAP - ' + new Date(),'', 5 | {htmlBody: htmlBody}); 6 | } 7 | -------------------------------------------------------------------------------- /SAP/Gmail Schemas/mail_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 39 | 40 | 41 |

42 | This a test for a Go-To action in Gmail. 43 |

44 | 45 | -------------------------------------------------------------------------------- /SAP/README.md: -------------------------------------------------------------------------------- 1 | SAP Demos 2 | ================ 3 | 4 | Sample SAP integrations. You'll need to create a [sample SAP account](http://scn.sap.com/docs/DOC-40986) at the SCN. 5 | 6 | You can watch the video of the [sample demos here](http://www.slideshare.net/SAPAppsPartner/tehcnical-webinar-technical-webinar-building-mashups-with-google-apps-and-sap-using-sap-netweaver-gateway) (start at about 1 hr in for Google Apps demos) 7 | 8 | These are not official Google samples. These are more of proof of concepts to support demos and other presentations. 9 | 10 | Please use this code as a guidline and not as production ready code. 11 | 12 | http://developers.google.com/apps-script 13 | 14 | http://script.google.com 15 | 16 | All code in this folder released under [Apache 2.0 license](http://www.apache.org/licenses/LICENSE-2.0). 17 | -------------------------------------------------------------------------------- /SAP/Sheets/Code.gs: -------------------------------------------------------------------------------- 1 | //sample instance 2 | //http://scn.sap.com/docs/DOC-40986 3 | 4 | 5 | function onOpen(){ 6 | SpreadsheetApp.getActiveSpreadsheet() 7 | .addMenu('SAP NetWeaver',[{name:'SAP Data Wizard', functionName:'showUI'}, 8 | null, 9 | {name:'Load Business Partners', functionName:'getBusinessPartnerData'}, 10 | {name:'Load Sales Orders', functionName:'getSalesOrderData'}, 11 | null, 12 | {name:'Load Items for Sales Order', functionName:'loadSalesItems'}, 13 | null, 14 | {name: 'Post to Google+', functionName:'googleplus'}]);//future ideas 15 | } 16 | 17 | function showUI(){ 18 | var ui = HtmlService.createTemplateFromFile('ui').evaluate().setSandboxMode(HtmlService.SandboxMode.NATIVE).setHeight(550); 19 | 20 | SpreadsheetApp.getActiveSpreadsheet().show(ui); 21 | } 22 | 23 | function returnDate(unix_timestamp){ 24 | //var unix_timestamp = '1370025951'; 25 | var date = new Date(unix_timestamp*1000); 26 | //Logger.log(date); 27 | return date; 28 | } 29 | 30 | function returnUnixtimestamp(dateString){ 31 | //var dateString = '2013-04-01'; 32 | var parts = dateString.match(/(\d+)/g); 33 | var date = new Date(parts[0], parts[1]-1, parts[2]); // months are 0-based 34 | var timestamp = date.getTime()/1000 + ''; 35 | return timestamp; 36 | } 37 | 38 | 39 | function createOrSetActiveSheet(sheetName){ 40 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 41 | var sheets = ss.getSheets(); 42 | var sheet; 43 | for(var i = 0;i 5 | 6 | 8 | 19 | 20 | Loading... 22 | 23 | SAP NetWeaver 24 | 25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 | 48 |
54 |
55 |
56 | 57 |
64 |
65 | 66 |
67 | 68 | 69 |
70 |
71 | 72 | 73 | 95 | -------------------------------------------------------------------------------- /Salesforce.com/OAuthAndUploadContactsToSalesforce.gs: -------------------------------------------------------------------------------- 1 | function onInstall(){ 2 | onOpen(); 3 | } 4 | 5 | function onOpen() { 6 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 7 | var menuEntries = [ {name: "Upload to SalesForce", functionName: "salesforceEntryPoint"}]; 8 | ss.addMenu("Salesforce.com", menuEntries); 9 | } 10 | 11 | function salesforceEntryPoint(){ 12 | if(isTokenValid()){ 13 | HTMLToOutput = '

Already have token

'; 14 | //HTMLToOutput += getData(); 15 | HTMLToOutput += uploadData(); 16 | } 17 | else {//we are starting from scratch or resetting 18 | HTMLToOutput = "

You need to login

click here to start
Re-open this window when you return."; 19 | } 20 | SpreadsheetApp.getActiveSpreadsheet().show(HtmlService.createHtmlOutput(HTMLToOutput)); 21 | } 22 | 23 | function doGet(e) { 24 | var HTMLToOutput; 25 | if(e.parameters.code){//if we get "code" as a parameter in, then this is a callback. we can make this more explicit 26 | getAndStoreAccessToken(e.parameters.code); 27 | HTMLToOutput = '

Finished with oAuth

You can close this window.'; 28 | } 29 | return HtmlService.createHtmlOutput(HTMLToOutput); 30 | } 31 | 32 | //do meaningful salesforce access here 33 | function getData(){ 34 | return runSOQL('SELECT+name+from+Account'); 35 | } 36 | 37 | function runSOQL(soql){ 38 | var getDataURL = UserProperties.getProperty(baseURLPropertyName) + '/services/data/v26.0/query/?q='+soql; 39 | var dataResponse = UrlFetchApp.fetch(getDataURL,getUrlFetchOptions()).getContentText(); 40 | return dataResponse; 41 | } 42 | 43 | function uploadData() { 44 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 45 | var sheet = ss.getSheets()[0]; 46 | var contactDataRange = ss.getDataRange(); 47 | var contactObjects = getRowsData(sheet, contactDataRange,1); 48 | var runningLog = '
Uploaded following:

'; 49 | for(var i = 1;i'; 60 | } 61 | return runningLog; 62 | } 63 | 64 | ////oAuth related code 65 | 66 | //hardcoded here for easily tweaking this. should move this to ScriptProperties or better parameterize them 67 | //step 1. we can actually start directly here if that is necessary 68 | var AUTHORIZE_URL = 'https://login.salesforce.com/services/oauth2/authorize'; 69 | //step 2. after we get the callback, go get token 70 | var TOKEN_URL = 'https://login.salesforce.com/services/oauth2/token'; 71 | 72 | //PUT YOUR OWN SETTINGS HERE 73 | var CLIENT_ID = '3MVG9y6x0357HlefXsMa7Fg0tYH0SLtBZwLWyhSP4hBGExVuUjoWLJ8rfZ1jyyw0ZRQt7H28rByHHJjRQlqMs'; 74 | var CLIENT_SECRET='SECRET_HERE'; 75 | var REDIRECT_URL= ScriptApp.getService().getUrl(); 76 | 77 | //this is the user propety where we'll store the token, make sure this is unique across all user properties across all scripts 78 | var tokenPropertyName = 'SALESFORCE_OAUTH_TOKEN'; 79 | var baseURLPropertyName = 'SALESFORCE_INSTANCE_URL'; 80 | 81 | 82 | //this is the URL where they'll authorize with salesforce.com 83 | //may need to add a "scope" param here. like &scope=full for salesforce 84 | function getURLForAuthorization(){ 85 | return AUTHORIZE_URL + '?response_type=code&client_id='+CLIENT_ID+'&redirect_uri='+REDIRECT_URL 86 | } 87 | 88 | function getAndStoreAccessToken(code){ 89 | var nextURL = TOKEN_URL + '?client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&grant_type=authorization_code&redirect_uri='+REDIRECT_URL+'&code=' + code; 90 | 91 | var response = UrlFetchApp.fetch(nextURL).getContentText(); 92 | var tokenResponse = JSON.parse(response); 93 | 94 | //salesforce requires you to call against the instance URL that is against the token (eg. https://na9.salesforce.com/) 95 | UserProperties.setProperty(baseURLPropertyName, tokenResponse.instance_url); 96 | //store the token for later retrival 97 | UserProperties.setProperty(tokenPropertyName, tokenResponse.access_token); 98 | } 99 | 100 | 101 | function getUrlFetchOptions() { 102 | var token = UserProperties.getProperty(tokenPropertyName); 103 | return { 104 | "contentType" : "application/json", 105 | "headers" : { 106 | "Authorization" : "Bearer " + token, 107 | "Accept" : "application/json" 108 | } 109 | }; 110 | } 111 | 112 | 113 | 114 | function getUrlFetchPOSTOptions(payload){ 115 | var token = UserProperties.getProperty(tokenPropertyName); 116 | return { 117 | "method": "post", 118 | "contentType" : "application/json", 119 | "payload" : payload, 120 | "headers" : { 121 | "Authorization" : "Bearer " + token 122 | } 123 | } 124 | } 125 | 126 | function isTokenValid() { 127 | var token = UserProperties.getProperty(tokenPropertyName); 128 | if(!token){ //if its empty or undefined 129 | return false; 130 | } 131 | return true; //naive check 132 | } -------------------------------------------------------------------------------- /Salesforce.com/RowUtilities.gs: -------------------------------------------------------------------------------- 1 | // getRowsData iterates row by row in the input range and returns an array of objects. 2 | // Each object contains all the data for a given row, indexed by its normalized column name. 3 | // Arguments: 4 | // - sheet: the sheet object that contains the data to be processed 5 | // - range: the exact range of cells where the data is stored 6 | // - columnHeadersRowIndex: specifies the row number where the column names are stored. 7 | // This argument is optional and it defaults to the row immediately above range; 8 | // Returns an Array of objects. 9 | function getRowsData(sheet, range, columnHeadersRowIndex) { 10 | columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1; 11 | var numColumns = range.getEndColumn() - range.getColumn() + 1; 12 | var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns); 13 | var headers = headersRange.getValues()[0]; 14 | return getObjects(range.getValues(), normalizeHeaders(headers)); 15 | } 16 | 17 | // getColumnsData iterates column by column in the input range and returns an array of objects. 18 | // Each object contains all the data for a given column, indexed by its normalized row name. 19 | // Arguments: 20 | // - sheet: the sheet object that contains the data to be processed 21 | // - range: the exact range of cells where the data is stored 22 | // - rowHeadersColumnIndex: specifies the column number where the row names are stored. 23 | // This argument is optional and it defaults to the column immediately left of the range; 24 | // Returns an Array of objects. 25 | function getColumnsData(sheet, range, rowHeadersColumnIndex) { 26 | rowHeadersColumnIndex = rowHeadersColumnIndex || range.getColumnIndex() - 1; 27 | var headersTmp = sheet.getRange(range.getRow(), rowHeadersColumnIndex, range.getNumRows(), 1).getValues(); 28 | var headers = normalizeHeaders(arrayTranspose(headersTmp)[0]); 29 | return getObjects(arrayTranspose(range.getValues()), headers); 30 | } 31 | 32 | 33 | // For every row of data in data, generates an object that contains the data. Names of 34 | // object fields are defined in keys. 35 | // Arguments: 36 | // - data: JavaScript 2d array 37 | // - keys: Array of Strings that define the property names for the objects to create 38 | function getObjects(data, keys) { 39 | var objects = []; 40 | for (var i = 0; i < data.length; ++i) { 41 | var object = {}; 42 | var hasData = false; 43 | for (var j = 0; j < data[i].length; ++j) { 44 | var cellData = data[i][j]; 45 | if (isCellEmpty(cellData)) { 46 | continue; 47 | } 48 | object[keys[j]] = cellData; 49 | hasData = true; 50 | } 51 | if (hasData) { 52 | objects.push(object); 53 | } 54 | } 55 | return objects; 56 | } 57 | 58 | // Returns an Array of normalized Strings. 59 | // Arguments: 60 | // - headers: Array of Strings to normalize 61 | function normalizeHeaders(headers) { 62 | var keys = []; 63 | for (var i = 0; i < headers.length; ++i) { 64 | var key = normalizeHeader(headers[i]); 65 | if (key.length > 0) { 66 | keys.push(key); 67 | } 68 | } 69 | return keys; 70 | } 71 | 72 | // Normalizes a string, by removing all alphanumeric characters and using mixed case 73 | // to separate words. The output will always start with a lower case letter. 74 | // This function is designed to produce JavaScript object property names. 75 | // Arguments: 76 | // - header: string to normalize 77 | // Examples: 78 | // "First Name" -> "firstName" 79 | // "Market Cap (millions) -> "marketCapMillions 80 | // "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored" 81 | function normalizeHeader(header) { 82 | var key = ""; 83 | var upperCase = false; 84 | for (var i = 0; i < header.length; ++i) { 85 | var letter = header[i]; 86 | if (letter == " " && key.length > 0) { 87 | upperCase = true; 88 | continue; 89 | } 90 | if (!isAlnum(letter)) { 91 | continue; 92 | } 93 | if (key.length == 0 && isDigit(letter)) { 94 | continue; // first character must be a letter 95 | } 96 | if (upperCase) { 97 | upperCase = false; 98 | key += letter.toUpperCase(); 99 | } else { 100 | key += letter.toLowerCase(); 101 | } 102 | } 103 | return key; 104 | } 105 | 106 | // Returns true if the cell where cellData was read from is empty. 107 | // Arguments: 108 | // - cellData: string 109 | function isCellEmpty(cellData) { 110 | return typeof(cellData) == "string" && cellData == ""; 111 | } 112 | 113 | // Returns true if the character char is alphabetical, false otherwise. 114 | function isAlnum(char) { 115 | return char >= 'A' && char <= 'Z' || 116 | char >= 'a' && char <= 'z' || 117 | isDigit(char); 118 | } 119 | 120 | // Returns true if the character char is a digit, false otherwise. 121 | function isDigit(char) { 122 | return char >= '0' && char <= '9'; 123 | } 124 | 125 | // Given a JavaScript 2d Array, this function returns the transposed table. 126 | // Arguments: 127 | // - data: JavaScript 2d Array 128 | // Returns a JavaScript 2d Array 129 | // Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]]. 130 | function arrayTranspose(data) { 131 | if (data.length == 0 || data[0].length == 0) { 132 | return null; 133 | } 134 | 135 | var ret = []; 136 | for (var i = 0; i < data[0].length; ++i) { 137 | ret.push([]); 138 | } 139 | 140 | for (var i = 0; i < data.length; ++i) { 141 | for (var j = 0; j < data[i].length; ++j) { 142 | ret[j][i] = data[i][j]; 143 | } 144 | } 145 | 146 | return ret; 147 | } 148 | -------------------------------------------------------------------------------- /Salesforce.com/ScanEmailToSalesforce.gs: -------------------------------------------------------------------------------- 1 | 2 | function scanEmail(){ 3 | //for performance sake, we'll scan top 3 threads for now. 4 | //a more intelligent trigger would know it last left off and then pick it up from there 5 | var threads = GmailApp.getInboxThreads(0, 5); 6 | for (var i = 0; i < threads.length; i++) { 7 | var message = threads[i].getMessages()[0]; //for now, only scan first message 8 | if(message.getAttachments().length < 1) 9 | continue; 10 | 11 | 12 | var attachment = message.getAttachments()[0]; //for now, only first attachment 13 | Logger.log(attachment.getName()); 14 | var base64content = Utilities.base64Encode(attachment.getAs('application/pdf').getBytes()); 15 | 16 | 17 | var fromAddress = message.getFrom(); 18 | //this will return of the the format - Arun Nagarajan 19 | var actualAddress = /<(.*?)>/.exec(fromAddress)[1]; 20 | var soql = encodeURIComponent("select Id from contact where Email='"+actualAddress+"'"); 21 | //Logger.log(actualAddress); 22 | var responseObject = Utilities.jsonParse(runSOQL(soql)); 23 | if(responseObject.totalSize !== 0){ 24 | var contactId = responseObject.records[0].Id; 25 | 26 | var payload = Utilities.jsonStringify( 27 | { 28 | 'Name' : attachment.getName() + '_GmailAttachment.pdf', 29 | 'ParentId' : contactId, 30 | 'body' : base64content 31 | }); 32 | Logger.log(contactId); 33 | var getDataURL = UserProperties.getProperty(baseURLPropertyName) + '/services/data/v26.0/sobjects/Attachment/'; 34 | var response = UrlFetchApp.fetch(getDataURL,getUrlFetchPOSTOptions(payload)).getContentText(); 35 | Logger.log(response); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Salesforce.com/readme.md: -------------------------------------------------------------------------------- 1 | Click the image below for video with more details - 2 | 3 | [![ScreenShot](https://i.ytimg.com/vi/9SEAmNDtlcA/hqdefault.jpg)](http://www.youtube.com/watch?v=9SEAmNDtlcA&list=PL68F511F6E3C122EB) -------------------------------------------------------------------------------- /ScriptDbVisualizer/Code.gs: -------------------------------------------------------------------------------- 1 | var db = ScriptDb.getMyDb(); 2 | 3 | function doGet() { 4 | return HtmlService.createHtmlOutputFromFile('ScriptDbConsole'); 5 | } 6 | 7 | function saveObjToDb(obj){ 8 | try{ 9 | var savedObj = db.save(obj); 10 | return "Saved ID - " + savedObj.getId(); 11 | }catch(e){ 12 | log(e); 13 | throw(e); 14 | } 15 | } 16 | 17 | function getCount(query){ 18 | try{ 19 | return "Current count for query is - " + db.count(query); 20 | }catch(e){ 21 | log(e); 22 | throw(e); 23 | } 24 | } 25 | 26 | function loadIDsFromDb(idlist){ 27 | try{ 28 | return db.load(idlist); 29 | }catch(e){ 30 | log(e); 31 | throw(e); 32 | } 33 | } 34 | 35 | function deleteByIds(idlist){ 36 | var obs_to_remove = loadIDsFromDb(idlist); 37 | var results = db.removeBatch(obs_to_remove, false); 38 | if (db.allOk(results)) { 39 | return "Delete by IDs successfull!"; 40 | } 41 | var failedObs = []; 42 | for (var i = 0; i < results.length; i++) { 43 | if (!results[i].successful()) { 44 | failedObs.push(obs_to_remove[i]); 45 | } 46 | } 47 | return "Failed to delete " + failedObs.length + " item(s) out of " + results.length; 48 | } 49 | 50 | function queryFromDb(query){ 51 | try{ 52 | var result = db.query(query); 53 | var response = {}; 54 | while (result.hasNext()) { 55 | var current = result.next(); 56 | response[current.getId()] = current; 57 | } 58 | return response; 59 | }catch(e){ 60 | log(e); 61 | throw(e); 62 | } 63 | } 64 | 65 | function deleteAll(query) { 66 | try{ 67 | while (true) { 68 | var result = db.query(query); 69 | if (result.getSize() == 0) { 70 | break; 71 | } 72 | while (result.hasNext()) { 73 | db.remove(result.next()); 74 | } 75 | } 76 | return "Delete for specified query successful!"; 77 | }catch(e){ 78 | log(e); 79 | throw(e); 80 | } 81 | } 82 | 83 | function log(msg){ 84 | //write to a logger here 85 | //DocsList.getFileById('0B0JNj_IM2wiPMS1lZTFhZjVjNC0yZTBjLTRiOWItYWVhMy0yYTU1ZjdkMGVkMGE').append(msg+'\n'); 86 | } -------------------------------------------------------------------------------- /ScriptDbVisualizer/ScriptDbConsole.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 90 | 91 |

Simple ScriptDb tester

92 | 93 | Enter a JavaScript Object to store, a ScriptDb query to run or IDs to lookup and press the appropriate button below
94 |
95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |

103 |
104 |

105 | By Arun Nagarajan 106 | 107 | 108 | -------------------------------------------------------------------------------- /ScriptDbVisualizer/readme.md: -------------------------------------------------------------------------------- 1 | Click the image below for video with more details - 2 | 3 | [![ScreenShot](https://i.ytimg.com/vi/aShR3R8--wU/hqdefault.jpg)](http://www.youtube.com/watch?v=aShR3R8--wU&list=PL68F511F6E3C122EB) 4 | -------------------------------------------------------------------------------- /Twilio/MakePhoneCall/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/entaq/GoogleAppsScript/672e8209d88d7fd390b1258a74c2e8bd5060ccd9/Twilio/MakePhoneCall/.DS_Store -------------------------------------------------------------------------------- /Twilio/MakePhoneCall/MakePhoneCall.gs: -------------------------------------------------------------------------------- 1 | var ACCOUNT_SID = 'ACd4e8e6872e581bf4cf560d37fb9059db'; 2 | var ACCOUNT_TOKEN = 'YOUR_TOKEN_HERE'; 3 | 4 | //This is a spreadsheet bound script that is also deployed as a web app 5 | 6 | //Have a menu or a button point this function to kick things off 7 | function readRows() { 8 | var sheet = SpreadsheetApp.getActiveSheet(); 9 | var rows = sheet.getDataRange(); 10 | var numRows = rows.getNumRows(); 11 | var values = rows.getValues(); 12 | 13 | //for testing purposes just make call against first row 14 | //expecting columns in this order - name, number, message - 15 | for (var i = 1; i <= 1/*numRows - 1*/; i++) { 16 | makePhoneCall(values[i][0],values[i][1],values[i][2]); 17 | } 18 | } 19 | 20 | function makePhoneCall(name,number, message){ 21 | //URL is the callback to the current service with the message 22 | var url = ScriptApp.getService().getUrl() + '?MSG'+message.replace(/ /g,'+'); //can't seem to send = in here 23 | Logger.log(url); 24 | var payload = { 25 | "From" : "2246773902" 26 | ,"To" : number 27 | ,"Url": url 28 | ,"Method" : "GET" 29 | }; 30 | 31 | var headers = { 32 | "Authorization" : "Basic " + Utilities.base64Encode(ACCOUNT_SID + ':' + ACCOUNT_TOKEN) 33 | }; 34 | 35 | // Because payload is a JavaScript object, it will be interpreted as 36 | // an HTML form. (We do not need to specify contentType; it will 37 | // automatically default to either 'application/x-www-form-urlencoded' 38 | // or 'multipart/form-data') 39 | 40 | var options = 41 | { 42 | "method" : "post", 43 | "payload" : payload, 44 | "headers" : headers 45 | }; 46 | Logger.log("calling " + name + " at " + number + " with messsage - " + message); 47 | var url = 'https://api.twilio.com/2010-04-01/Accounts/'+ACCOUNT_SID+'/Calls.json'; 48 | var response = UrlFetchApp.fetch(url, options); 49 | //Logger.log(response.getResponseCode()); 50 | //Logger.log(response.getContentText()); 51 | } 52 | 53 | //entry point to the call back 54 | function doGet(args){ 55 | var msg = ''; 56 | for (var p in args.parameters) { 57 | //there are many incoming query params. lets find ours starting with a MSG 58 | if(p.indexOf('MSG') > -1){ 59 | msg += p.replace('MSG',''); 60 | break; 61 | } 62 | // there HAS to tbe a better way to get the entire query string! 63 | } 64 | 65 | var t = HtmlService.createTemplateFromFile("twiml.html"); 66 | t.msg = msg; 67 | var content = t.evaluate().getContent(); 68 | return ContentService.createTextOutput(content).setMimeType(ContentService.MimeType.XML); 69 | } 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /Twilio/MakePhoneCall/twiml.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://api.twilio.com/cowbell.mp3 4 | -------------------------------------------------------------------------------- /Twilio/RecieveSMS/RecieveSMS.gs: -------------------------------------------------------------------------------- 1 | //This is deployed as a web app and in the Twilio interface you'll point incoming SMS to the deployed URL 2 | //deploy it to run as you and allow anyone (even anonymous) can access it 3 | 4 | function doGet(args) { 5 | var spreadsheetID = 'YOUR_SPREAD_SHEETID'; 6 | 7 | //Incoming params are documented here - https://www.twilio.com/docs/api/twiml/sms/twilio_request 8 | var vote = args.parameter.Body; 9 | var from = args.parameter.From; 10 | 11 | //sample poll here for favorite football team in NYC 12 | var actualVote; 13 | switch (vote.toLowerCase()) { 14 | case "a": 15 | actualVote = 'Giants'; 16 | break; 17 | case "b": 18 | actualVote = 'Jets'; 19 | break; 20 | default: 21 | actualVote = 'Dont care'; 22 | } 23 | 24 | SpreadsheetApp.openById(spreadsheetID).appendRow([from,actualVote,vote]); 25 | 26 | return ContentService.createTextOutput('').setMimeType(ContentService.MimeType.TEXT); 27 | //empty message sends no response. save a penny! 28 | 29 | //other funn stuff - quickly translate 30 | //return ContentService.createTextOutput(LanguageApp.translate(args.parameter.Body, "en", "es")).setMimeType(ContentService.MimeType.TEXT); 31 | }; 32 | 33 | 34 | -------------------------------------------------------------------------------- /Twilio/readme.md: -------------------------------------------------------------------------------- 1 | Click the image below for video with more details - 2 | 3 | [![ScreenShot](https://i.ytimg.com/vi/j0wjM1Ds3lc/hqdefault.jpg)](http://www.youtube.com/watch?v=j0wjM1Ds3lc&list=PL68F511F6E3C122EB) 4 | -------------------------------------------------------------------------------- /WhiteHouseHackday/Code.gs: -------------------------------------------------------------------------------- 1 | function onOpen(){ 2 | SpreadsheetApp.getActiveSpreadsheet() 3 | .addMenu('We the people',[{name:'Search Petitions', functionName:'showUI'}, 4 | {name:'Load Signatures', functionName:'loadSignatures'}, 5 | null, 6 | {name: 'Twitter trending', functionName:'twitter'}]);//future ideas 7 | } 8 | 9 | function showUI(){ 10 | var ui = HtmlService.createTemplateFromFile('ui').evaluate().setSandboxMode(HtmlService.SandboxMode.NATIVE).setHeight(550); 11 | 12 | SpreadsheetApp.getActiveSpreadsheet().show(ui); 13 | } 14 | 15 | function returnDate(unix_timestamp){ 16 | //var unix_timestamp = '1370025951'; 17 | var date = new Date(unix_timestamp*1000); 18 | //Logger.log(date); 19 | return date; 20 | } 21 | 22 | function returnUnixtimestamp(dateString){ 23 | //var dateString = '2013-04-01'; 24 | var parts = dateString.match(/(\d+)/g); 25 | var date = new Date(parts[0], parts[1]-1, parts[2]); // months are 0-based 26 | var timestamp = date.getTime()/1000 + ''; 27 | return timestamp; 28 | } 29 | 30 | 31 | function createOrSetActiveSheet(sheetName){ 32 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 33 | var sheets = ss.getSheets(); 34 | var sheet; 35 | for(var i = 0;i 2 | We the People 3 | 4 | 6 | 17 | 18 | 40 | 41 | 42 | Loading... 44 | 45 | Search Petitions 46 | 47 |
48 |
49 | 50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 | 62 |
63 |
64 |
65 | 66 |
67 |
68 |
69 | 70 |
112 |
113 |
114 | 115 |
122 |
123 | 124 |
125 | 126 | 127 |
128 |
129 | -------------------------------------------------------------------------------- /YouTube/CreateGDLCalendar.gs: -------------------------------------------------------------------------------- 1 | function createCalendar(){ 2 | var API_KEY = 'YOU_API_KEY_HERE'; 3 | var cal = CalendarApp.createCalendar('GDL Calendar'); 4 | var url = 'https://www.googleapis.com/youtube/v3/activities?' 5 | +'part=snippet&channelId=UC_x5XG1OV2P6uZZ5FSM9Ttw&maxResults=20&publishedBefore=2013-02-25T00:00:00.0Z' 6 | +'&key='+API_KEY; 7 | var response = UrlFetchApp.fetch(url).getContentText(); 8 | 9 | var responseObject = JSON.parse(response); 10 | 11 | for(var i=0;responseObject.items && iclick here to start"; 73 | } 74 | 75 | SpreadsheetApp.getActiveSpreadsheet().show(HtmlService.createHtmlOutput(HTMLToOutput)); 76 | } 77 | 78 | function doGet(e) { 79 | var HTMLToOutput; 80 | if(e.parameters.code){//if we get "code" as a parameter in, then this is a callback. we can make this more explicit 81 | getAndStoreAccessToken(e.parameters.code); 82 | HTMLToOutput = '

Finished with oAuth. You can close the window now

'; 83 | } 84 | 85 | else {//we are starting from scratch or resetting 86 | return HtmlService.createHtmlOutput("

Lets start with oAuth

click here to start"); 87 | } 88 | 89 | return HtmlService.createHtmlOutput(HTMLToOutput); 90 | } 91 | 92 | /** 93 | This assumes the first 6 rows of the active sheet is these two columns. 94 | ids - channel==UC_x5XG1OV2P6uZZ5FSM9Ttw 95 | start-date - 2013-01-01 96 | end-date - 2013-02-25 97 | metrics - estimatedMinutesWatched 98 | dimensions - 30DayTotals 99 | filters - country==US 100 | **/ 101 | function getData(){ 102 | var ss = SpreadsheetApp.getActiveSpreadsheet(); 103 | 104 | var ids = ss.getActiveSheet().getRange(1, 2).getValue(); 105 | var startdate = ss.getActiveSheet().getRange(2, 2).getValue(); 106 | var enddate = ss.getActiveSheet().getRange(3, 2).getValue(); 107 | var metrics = ss.getActiveSheet().getRange(4, 2).getValue(); 108 | var dimensions = ss.getActiveSheet().getRange(5, 2).getValue(); 109 | var filters = ss.getActiveSheet().getRange(6, 2).getValue(); 110 | 111 | var base_url = 'https://www.googleapis.com/youtube/analytics/v1/reports?key='+API_KEY; 112 | var getDataURL = base_url + '&ids='+ids+'&start-date='+startdate+'&end-date='+enddate+'&metrics='+metrics+'&dimensions='+dimensions+'&filters='+filters; 113 | Logger.log(getDataURL); 114 | 115 | var dataResponse = UrlFetchApp.fetch(getDataURL,getUrlFetchOptions()).getContentText(); 116 | var dataObj = JSON.parse(dataResponse); 117 | var headers = []; 118 | for(var i = 0;dataObj.columnHeaders && iProject->User Properties 174 | function isTokenValid() { 175 | var token = UserProperties.getProperty(tokenPropertyName); 176 | if(!token){ //if its empty or undefined 177 | return false; 178 | } 179 | return true; //naive check 180 | 181 | //if your API has a more fancy token checking mechanism, use it. for now we just check to see if there is a token. 182 | /* 183 | var responseString; 184 | try{ 185 | responseString = UrlFetchApp.fetch(BASE_URI+'/api/rest/system/session/check',getUrlFetchOptions(token)).getContentText(); 186 | }catch(e){ //presumably an HTTP 401 will go here 187 | return false; 188 | } 189 | if(responseString){ 190 | var responseObject = JSON.parse(responseString); 191 | return responseObject.authenticated; 192 | } 193 | return false;*/ 194 | } -------------------------------------------------------------------------------- /YouTube/readme.md: -------------------------------------------------------------------------------- 1 | Click the image below for video with more details - 2 | 3 | [![ScreenShot](https://i.ytimg.com/vi/VVhsK5jH6u8/hqdefault.jpg)](http://www.youtube.com/watch?v=VVhsK5jH6u8&list=PL68F511F6E3C122EB) --------------------------------------------------------------------------------