├── LICENSE.txt ├── README.md ├── addons └── sync_spreadsheets │ ├── files │ ├── http.gd │ ├── sheet_resource.gd │ ├── sheets_resource.gd │ ├── sync.gd │ └── sync.tscn │ ├── plugin.cfg │ └── plugin.gd └── docs └── sync_spreadsheets.png /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Elias Guyd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot Sync Spreadsheets 2 | 3 | This is a plugin for Godot 4+ that allows to synchronize CSV files with Google Spreadsheets from godot. 4 | 5 | It's useful for having translations or collaborative databases on Spreadsheets and synchronize them with your game when you create without downloading on every change, just click a button from editor. 6 | 7 | ## How install this plugin? 8 | 9 | Paste the sync_spreadshets on addons folder on your godot project (create an addons folder if you don't have one). 10 | 11 | Go to "Project" -> "Project Settings" -> "Plugins" -> "Sync Spreadsheets" -> Put "Enabled" to On 12 | 13 | 14 | ## How use this plugin 15 | 16 | 0- You need a CSV file on the game files to start. Just create a blank one, or download the first time from Google Spreadsheets. 17 | 18 | 1- Share the spreadsheet (view only is enough) and get the Sheet ID from the link: 19 | Link example: https://docs.google.com/spreadsheets/d/{Sheet ID}/edit?gid={gid} 20 | 21 | 2- In the 'Sync CSV Spreadsheets' bottom panel, click 'Edit CSV Spreadsheets'. 22 | 23 | 3- Configure the CSV files in the inspector. Paste the Sheet ID and set the Sheet Name or Gid (only one is needed). 24 | 25 | 4- In the 'Sync CSV Spreadsheets' bottom panel, click 'Sync Now'. To save the config, click 'Save on disk' (config is saved in user://). 26 | 27 | 5- Done! Every time you click 'Sync Now', it will automatically pull the latest CSV from Google Spreadsheets. 28 | 29 | ## Bugs? Ideas? 30 | 31 | Open an issue on github! -------------------------------------------------------------------------------- /addons/sync_spreadsheets/files/http.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Node 3 | class_name HTTP 4 | 5 | const HEADER = ["Content-Type: application/json; charset=UTF-8"] 6 | 7 | 8 | func req(url: String, ## url ended in / 9 | callback: Callable, ## callback(response : Dictionary) 10 | method : HTTPClient.Method = HTTPClient.METHOD_GET, 11 | query: Dictionary = {}, 12 | body: Dictionary = {}, 13 | headers : Array = HEADER, 14 | path : String = ""): 15 | 16 | var http_request = HTTPRequest.new() 17 | add_child(http_request) 18 | http_request.request_completed.connect(func(r,c,h,b): _req_completed(r,c,h,b, callback)) 19 | 20 | var QUERY = "?" + "&".join(query.keys().map(func(k): return k.uri_encode() + "=" + str(query[k]).uri_encode())) 21 | var error: Error 22 | var body_str = JSON.new().stringify(body) if body else "" 23 | 24 | if path != "": 25 | http_request.set_download_file(path) 26 | error = http_request.request(url + QUERY, headers, method, body_str) 27 | if error != OK: 28 | push_error("An error occurred in the HTTP request. This should be not happened, is a code error!") 29 | else: 30 | pass # print("New HTTP request!") 31 | 32 | 33 | func _req_completed(result, response_code, headers, body, callback: Callable): 34 | #print("REQ COMPLETED. result: " + str(result) + " response_code: " + str(response_code)) 35 | var err = OK 36 | if result != HTTPRequest.RESULT_SUCCESS: 37 | printerr("ERROR: You are OFFLINE") 38 | err = result 39 | elif response_code >= 400: 40 | printerr("ERROR: Request INVALID") 41 | err = response_code 42 | else: 43 | pass # printt("ONLINE") 44 | 45 | var json = JSON.new() 46 | json.parse(body.get_string_from_utf8()) 47 | var response = json.get_data() 48 | callback.call(response, err) 49 | 50 | if err != OK: 51 | printerr("ERROR response: ", response) 52 | -------------------------------------------------------------------------------- /addons/sync_spreadsheets/files/sheet_resource.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name SheetResource 3 | 4 | @export var csv_path : String 5 | @export var sheet_id : String 6 | @export var sheet_name : String 7 | @export var sheet_gid : String 8 | @export var updated_at : String 9 | @export var update_on_auto_sync : bool 10 | 11 | func _init(p_csv_path = "", p_sheet_id = "", p_sheet_name = "", p_sheet_gid = "", p_updated_at = "", p_update_on_auto_sync = true): 12 | csv_path = p_csv_path 13 | sheet_id = p_sheet_id 14 | sheet_name = p_sheet_name 15 | sheet_gid = p_sheet_gid 16 | updated_at = p_updated_at 17 | update_on_auto_sync = p_update_on_auto_sync 18 | -------------------------------------------------------------------------------- /addons/sync_spreadsheets/files/sheets_resource.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name SheetsResource 3 | 4 | @export var sheets : Array[SheetResource] = [] 5 | 6 | func _init(p_sheets : Array[SheetResource] = []): 7 | sheets = p_sheets 8 | -------------------------------------------------------------------------------- /addons/sync_spreadsheets/files/sync.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Control 3 | 4 | @onready var http : HTTP = $HTTP 5 | 6 | var sheets_resource : Resource 7 | 8 | # TODO: configure this options 9 | var options = { 10 | auto_sync_on_open_tab = true, 11 | auto_sync_on_play = false, 12 | auto_scan_for_csv_files = true, 13 | } 14 | 15 | var config = ConfigFile.new() 16 | 17 | 18 | func _ready(): 19 | sheets_resource = SheetsResource.new() 20 | _load_config() 21 | _sync_csv_files() 22 | _save_config() 23 | 24 | 25 | func _sync_csv_files(): 26 | var csv_paths = _get_all_file_paths("res://", ".csv") 27 | 28 | for csv_path in csv_paths: 29 | var exist = sheets_resource.sheets.filter(func(s): return s.csv_path == csv_path) 30 | if not exist: 31 | var new_sheet_resource : Resource = SheetResource.new(csv_path, "", "", "", Time.get_datetime_string_from_system(), true) 32 | sheets_resource.sheets.append(new_sheet_resource) 33 | 34 | for sheet in sheets_resource.sheets: 35 | if csv_paths.has(sheet.csv_path): 36 | _sync_sheet(sheet) 37 | 38 | 39 | func _load_config(): 40 | var err = config.load("user://_sync_csv_spreadsheets.cfg") 41 | if err == OK: 42 | config.get_value("Options", "options", options) 43 | 44 | if ResourceLoader.exists("user://_sync_csv_spreadsheets.res"): 45 | var new_sheets_resource = ResourceLoader.load("user://_sync_csv_spreadsheets.res") 46 | if new_sheets_resource is SheetsResource: 47 | sheets_resource = new_sheets_resource 48 | 49 | _save_config() 50 | 51 | 52 | func _save_config(): 53 | ResourceSaver.save(sheets_resource, "user://_sync_csv_spreadsheets.res") 54 | config.set_value("Options", "options", options) 55 | config.save("user://_sync_csv_spreadsheets.cfg") 56 | #print("Sync CSV Spreadsheets configurations saved!") 57 | 58 | 59 | func _sync_sheet(sheet : SheetResource): 60 | if sheet.sheet_id and sheet.csv_path: 61 | var url = "https://docs.google.com/spreadsheets/d/" + sheet.sheet_id + "/gviz/tq" 62 | if sheet.sheet_name: 63 | http.req(url, func(r, err): _sync_sheet_callback(r, err, sheet), HTTPClient.METHOD_GET, { "tqx": "out:csv", "sheet": sheet.sheet_name }, {}, [], sheet.csv_path) 64 | elif sheet.sheet_gid: 65 | http.req(url, func(r, err): _sync_sheet_callback(r, err, sheet), HTTPClient.METHOD_GET, { "tqx": "out:csv", "gid": sheet.sheet_gid }, {}, [], sheet.csv_path) 66 | else: 67 | return 68 | sheet.updated_at = Time.get_datetime_string_from_system() 69 | return sheet 70 | 71 | 72 | func _sync_sheet_callback(r, err, sheet): 73 | sheet.updated_at = Time.get_datetime_string_from_system() 74 | _save_config() 75 | 76 | 77 | func _sync(url, sheet_name, csv_path): 78 | http.req(url, func(r, err): printt(r, err), HTTPClient.METHOD_GET, { "tqx": "out:csv", "sheet": sheet_name }, {}, [], csv_path) 79 | 80 | 81 | func _get_all_file_paths(path: String, file_extension: String = "") -> Array[String]: 82 | var file_paths: Array[String] = [] 83 | var dir = DirAccess.open(path) 84 | dir.list_dir_begin() 85 | var file_name = dir.get_next() 86 | while file_name != "": 87 | var file_path = path + "/" + file_name 88 | if dir.current_is_dir(): 89 | file_paths += _get_all_file_paths(file_path, file_extension) 90 | else: 91 | if file_path.ends_with(file_extension): 92 | file_paths.append(file_path) 93 | file_name = dir.get_next() 94 | dir.list_dir_end() 95 | return file_paths 96 | 97 | 98 | func _on_open_sheets_resource_pressed(): 99 | #printt("Showing", sheets_resource) 100 | EditorInterface.get_inspector().resource_selected.emit(sheets_resource, "user://_sync_csv_spreadsheets.res") 101 | 102 | 103 | func _on_save_sheets_resource_pressed(): 104 | _save_config() 105 | #printt("Saved: ", sheets_resource.sheets[0].sheet_id) 106 | _load_config() 107 | #printt("Loaded: ", sheets_resource.sheets[0].sheet_id) 108 | 109 | 110 | func _on_sync_now_pressed(): 111 | _sync_csv_files() 112 | -------------------------------------------------------------------------------- /addons/sync_spreadsheets/files/sync.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://c175dpomx5ru4"] 2 | 3 | [ext_resource type="Script" path="res://addons/sync_spreadsheets/files/sync.gd" id="1_bjlf8"] 4 | [ext_resource type="Script" path="res://addons/sync_spreadsheets/files/http.gd" id="2_3wys5"] 5 | 6 | [node name="Sync" type="VBoxContainer"] 7 | anchors_preset = 15 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | grow_horizontal = 2 11 | grow_vertical = 2 12 | script = ExtResource("1_bjlf8") 13 | 14 | [node name="HTTP" type="Node" parent="."] 15 | script = ExtResource("2_3wys5") 16 | 17 | [node name="VBoxContainer" type="HBoxContainer" parent="."] 18 | layout_mode = 2 19 | 20 | [node name="OpenSheetsResource" type="Button" parent="VBoxContainer"] 21 | layout_mode = 2 22 | text = "Edit CSV Spreadsheets" 23 | 24 | [node name="SaveSheetsResource" type="Button" parent="VBoxContainer"] 25 | layout_mode = 2 26 | text = "Save on disk" 27 | 28 | [node name="SyncNow" type="Button" parent="VBoxContainer"] 29 | layout_mode = 2 30 | text = "Sync Now" 31 | 32 | [connection signal="pressed" from="VBoxContainer/OpenSheetsResource" to="." method="_on_open_sheets_resource_pressed"] 33 | [connection signal="pressed" from="VBoxContainer/SaveSheetsResource" to="." method="_on_save_sheets_resource_pressed"] 34 | [connection signal="pressed" from="VBoxContainer/SyncNow" to="." method="_on_sync_now_pressed"] 35 | -------------------------------------------------------------------------------- /addons/sync_spreadsheets/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Sync CSV with Google Spreadsheets" 4 | description="[Instructions] 5 | # Sync CSV with Google Spreadsheets 6 | # Sync all local CSV files with Google Spreadsheets 7 | 8 | [How to use?] 9 | 1- Share the spreadsheet (view only is enough) and get the Sheet ID from the link: 10 | Link example: https://docs.google.com/spreadsheets/d/{Sheet ID}/edit?gid={gid} 11 | 12 | 2- In the 'Sync CSV Spreadsheets' bottom panel, click 'Edit CSV Spreadsheets'. 13 | 14 | 3- Configure the CSV files in the inspector. Paste the Sheet ID and set the Sheet Name or Gid (only one is needed). 15 | 16 | 4- In the 'Sync CSV Spreadsheets' bottom panel, click 'Sync Now'. To save the config, click 'Save on disk' (config is saved in user://). 17 | 18 | 5- Done! Every time you click 'Sync Now', it will automatically pull the latest CSV from Google Spreadsheets." 19 | author="@EliasGuyd" 20 | version="1.0.0" 21 | script="plugin.gd" 22 | 23 | 24 | -------------------------------------------------------------------------------- /addons/sync_spreadsheets/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | const SyncScene = preload("res://addons/sync_spreadsheets/files/sync.tscn") 5 | 6 | var sync_instance 7 | 8 | func _enter_tree(): 9 | sync_instance = SyncScene.instantiate() 10 | add_control_to_bottom_panel(sync_instance, "Sync CSV Spreadsheets", Shortcut.new()) 11 | 12 | 13 | func _exit_tree(): 14 | if sync_instance: 15 | remove_control_from_bottom_panel(sync_instance) 16 | sync_instance.queue_free() 17 | -------------------------------------------------------------------------------- /docs/sync_spreadsheets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eliasc9/godot_sync_spreadsheets/5da18431a4c4bf92a1a4f33674a1ff9d1437ed53/docs/sync_spreadsheets.png --------------------------------------------------------------------------------