|
|
Line 3: |
Line 3: |
| | image = https://dlhb.gamebrew.org/3dshomebrew/Line-for-3DS.png|250px | | | image = https://dlhb.gamebrew.org/3dshomebrew/Line-for-3DS.png|250px |
| | type = Other Apps | | | type = Other Apps |
| | version = v1.3.1 | | | version = v1.7.2 |
| | licence = Mixed | | | licence = Mixed |
| | author = Core-2-Extreme | | | author = Core-2-Extreme |
| | website = https://gbatemp.net/threads/v0-2-0-release-guide-line-for-3ds.539530/ | | | website = https://gbatemp.net/threads/v0-2-0-release-guide-line-for-3ds.539530/ |
| | download = https://dlhb.gamebrew.org/3dshomebrew/Line_for_3DS-1.3.1.rar | | | download = https://dlhb.gamebrew.org/3dshomebrew/Line_for_3DSv1.7.2.7z |
| | source = https://github.com/Core-2-Extreme/Line_for_3DS | | | source = https://github.com/Core-2-Extreme/Line_for_3DS |
| }} | | }} |
| <youtube>1T90ZQxDnOc</youtube> | | <youtube>5K2fCr0lyoM</youtube> |
|
| |
|
| = Line for 3DS = | | = Line for 3DS = |
|
| |
|
| https://user-images.githubusercontent.com/45873899/77541203-a9977e80-6ee7-11ea-8479-53aed389ab64.png | | https://user-images.githubusercontent.com/45873899/77541203-a9977e80-6ee7-11ea-8479-53aed389ab64.png |
| | |
| | ==Supported function:== |
| | |
| | * Send text ✅ |
| | * Send image ✅ (v1.4.0) |
| | * Send video ✅(*2) (v1.4.0) |
| | * Send sound ✅(*2) (v1.4.0) |
| | * Send sticker ✅ (*1) (v1.3.0) |
| | * Receive text ✅ |
| | * Receive (View) image ✅ (v1.2.0) |
| | * Receive (Play) video ✅ (v1.7.0) |
| | * Receive (Play) sound ✅ (v1.7.0) |
| | * Receive (View) sticker ✅(*1) (v1.3.0) |
| | * View old log ✅ (~v1.5.2 Max 300 logs) (v1.6.0~ Max 4000 logs) |
| | * Auto update ✅ (v0.2.0) |
| | * Group chat ✅ |
| | * Night mode ✅ (v0.2.0) |
| | * Password ✅ (v0.3.0) |
| | * Save log to SD card ✅ (v0.3.0) |
|
| |
|
| == Official Discord == | | == Official Discord == |
Line 20: |
Line 39: |
| [https://discord.gg/EqK3Kpb https://discord.gg/EqK3Kpb] | | [https://discord.gg/EqK3Kpb https://discord.gg/EqK3Kpb] |
|
| |
|
| == Index == | | == How to use == |
|
| |
|
| * [https://github.com/Core-2-Extreme/Line_for_3DS#Patch-note Patch-note] | | '''*1 The stickers must be included this list''' |
| * [https://github.com/Core-2-Extreme/Line_for_3DS#summary Summary]
| | [https://dlhb.gamebrew.org/3dshomebrew/sticker_list.pdf sticker_list.pdf] |
| * [https://github.com/Core-2-Extreme/Line_for_3DS#How-to-use How-to-use]
| |
|
| |
|
| == Patch note ==
| | '''*2 Google drive URL will be sent(not embed).''' |
| | |
| V1.3.1
| |
| * 【App】 Ver 1.3.1
| |
| * 【Line】 Fixed- Common occurring [FSUSER_OpenArchive failed] error has been solved.
| |
| * 【Line】 Fixed- Image display now activated by touching the message. | |
| * 【Setting】 Fixed- Setting menu framerate now improved.
| |
| * 【Image viewer】 Fixed- Image display speed now improved.
| |
| | |
| v1.2.0
| |
| * Fix- Some crashes
| |
| * Fix- Some settings do not work
| |
| * Fix- Log download (GAS processing) speed has been improved(GAS update required)
| |
| * Add- Image viewer
| |
| | |
| == Summary ==
| |
| | |
| = [https://www.youtube.com/watch?v=5K2fCr0lyoM Video] =
| |
| | |
| * Send text ?
| |
| * Send picture ?
| |
| * Send video ?
| |
| * Send sound ?
| |
| * Send stamp ?
| |
| * Receive text ?
| |
| * Receive (View) picture ? (v1.2.0)
| |
| * Receive (Play) video ?
| |
| * Receive (Play) sound ?
| |
| * Receive (View) stamp ?
| |
| * View old log ? (Until max 300 logs)
| |
| * Auto update ? (v0.2.0)
| |
| * Group chat ?
| |
| * Night mode ? (v0.2.0)
| |
| * Password ? (v0.3.0)
| |
| * Save log to SD card ? (v0.3.0)
| |
| | |
| == How to use ==
| |
| | |
| How to install and use for version 1.3.0~
| |
|
| |
|
| | ===How to setup Google apps script=== |
| '''Please access this site.<br /> | | '''Please access this site.<br /> |
| [https://developers.line.biz/en/ <span style="font-size: 15px"><span style="color: rgb(0, 0, 0)">'''https://developers.line.biz/en/'''</span></span>]<br /> | | [https://developers.line.biz/en/ <span style="font-size: 15px"><span style="color: rgb(0, 0, 0)">'''https://developers.line.biz/en/'''</span></span>]<br /> |
Line 104: |
Line 85: |
| '''<br /> | | '''<br /> |
|
| |
|
| <pre>
| |
|
| |
| Code:
| |
|
| |
|
| | <ul class="tabs" data-tab> |
| | <li class="tab-title active"><a href="#panel1">For Ver 1.7.0</a></li> |
| | <li class="tab-title"><a href="#panel2">For Ver 1.6.0</a></li> |
| | <li class="tab-title"><a href="#panel3">For Ver 1.5.0~1.5.2</a></li> |
| | </ul> |
| | <div class="tabs-content"> |
| | <div class="content active" id="panel1"> |
| | <p>This is the first panel of the basic tab example. You can place all sorts of content here including a grid.</p> |
| | </div> |
| | <div class="content" id="panel2"> |
| | <p>This is the second panel of the basic tab example. This is the second panel of the basic tab example.</p> |
| | </div> |
| | <div class="content" id="panel3"> |
| | <p>This is the third panel of the basic tab example. This is the third panel of the basic tab example.</p> |
| | </div> |
| </div> | | </div> |
| <pre>var ACCESS_TOKEN = "Your acces token here";
| |
| var open_sheet_id = "Your sheet id here";
| |
| var account_name_of_3ds = "Your 3ds's account name here";
| |
| var script_password = "Your google apps script password here";
| |
|
| |
|
| var gas_ver = 3;//Do **NOT** edit this value.
| |
| function log_save(message, user_name, write_sheet_name)
| |
| {
| |
| var sheet_pos = 1;
| |
| var spreadsheet = SpreadsheetApp.openById(open_sheet_id);
| |
| var write_sheet = spreadsheet.getSheetByName(write_sheet_name);
| |
|
| |
|
| if(!write_sheet)
| |
| {
| |
| spreadsheet.insertSheet(write_sheet_name);
| |
| write_sheet = spreadsheet.getSheetByName(write_sheet_name);
| |
| }
| |
|
| |
| sheet_pos = get_cache_pos(write_sheet);
| |
|
| |
| while(true)
| |
| {
| |
| var sheet_data = write_sheet.getRange("A" + sheet_pos).getValue();
| |
|
| |
|
| if(sheet_data == "")
| |
| {
| |
| write_sheet.getRange("A" + sheet_pos).setValue("" + user_name + " : " + message);
| |
| break;
| |
| }
| |
| else
| |
| sheet_pos++;
| |
| }
| |
|
| |
| write_cache_pos(write_sheet, (sheet_pos + 1));
| |
| }
| |
|
| |
|
| function log_read(id)
| |
| {
| |
| var sheet_start = 1;
| |
| var return_data;
| |
| var sheet_data;
| |
| var spreadsheet = SpreadsheetApp.openById(open_sheet_id);
| |
| var read_sheet = spreadsheet.getSheetByName(id);
| |
| sheet_start = get_cache_pos(read_sheet);
| |
|
| |
|
| while(true)
| |
| {
| |
| sheet_data = read_sheet.getRange("A" + sheet_start).getValue();
| |
| if(sheet_data == "")
| |
| {
| |
| write_cache_pos(read_sheet, sheet_start);
| |
| if(sheet_start >= 301)
| |
| sheet_start = (sheet_start - 300);
| |
| else
| |
| sheet_start = 1;
| |
|
| |
| break;
| |
| }
| |
| else
| |
| sheet_start++;
| |
| }
| |
|
| |
| sheet_data = read_sheet.getRange(sheet_start, 1, 300).getValues();
| |
| for(var i = 0; i < 300; i++)
| |
| {
| |
| if(sheet_data[i] == "")
| |
| break;
| |
|
| |
| return_data += sheet_data[i];
| |
| }
| |
| return return_data;
| |
| }
| |
|
| |
|
| function get_cache_pos(sheet_object)
| |
| {
| |
| var cached_sheet_pos = sheet_object.getRange("B1").getValue();
| |
| var sheet_data;
| |
|
| |
| if(parseInt(cached_sheet_pos) > 0)
| |
| {
| |
| cached_sheet_pos = parseInt(cached_sheet_pos);
| |
| sheet_data = sheet_object.getRange("A" + (cached_sheet_pos - 1)).getValue();
| |
|
| |
| if(sheet_data != "")
| |
| return cached_sheet_pos;
| |
| }
| |
| return 1;
| |
| }
| |
|
| |
|
| function write_cache_pos(sheet_object, cache_data)
| |
| {
| |
| sheet_object.getRange("B1").setValue(cache_data);
| |
| }
| |
|
| |
|
| function send_msg(id, send_message, time)
| | How to install and use for version 1.3.0~ |
| {
| |
| var return_message = "Success";
| |
| var response;
| |
|
| |
| var url = 'https://api.line.me/v2/bot/message/push';
| |
| response = UrlFetchApp.fetch(url, {
| |
| 'headers': {
| |
| 'Content-Type': 'application/json; charset=UTF-8',
| |
| 'Authorization': 'Bearer ' + ACCESS_TOKEN,
| |
| },
| |
| 'method': 'post',
| |
| 'payload': JSON.stringify({
| |
| "to": id,
| |
| "messages":
| |
| [
| |
| {
| |
| "text": send_message,
| |
| "type": "text",
| |
| }],
| |
| 'notificationDisabled': 'false',
| |
| }),
| |
| muteHttpExceptions: true,
| |
| });
| |
|
| |
| if(response.getResponseCode() != 200)
| |
| {
| |
| var cache = "***Send failed. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message + "*** ";
| |
| cache += send_message;
| |
| send_message = cache;
| |
| return_message = "Send message failed. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
| |
| }
| |
| send_message += "(" + time + ")";
| |
| log_save(send_message, account_name_of_3ds, id);
| |
| return return_message;
| |
| }
| |
| | |
| function send_sticker(id, package_id, sticker_id, time)
| |
| {
| |
| var return_message = "Success";
| |
| var send_message = "";
| |
| var response;
| |
|
| |
| var url = 'https://api.line.me/v2/bot/message/push';
| |
| response = UrlFetchApp.fetch(url, {
| |
| 'headers': {
| |
| 'Content-Type': 'application/json; charset=UTF-8',
| |
| 'Authorization': 'Bearer ' + ACCESS_TOKEN,
| |
| },
| |
| 'method': 'post',
| |
| 'payload': JSON.stringify({
| |
| "to": id,
| |
| "messages":
| |
| [
| |
| {
| |
| "type": "sticker",
| |
| "packageId": package_id,
| |
| "stickerId": sticker_id,
| |
| }],
| |
| 'notificationDisabled': 'false',
| |
| }),
| |
| muteHttpExceptions: true,
| |
| });
| |
| if(response.getResponseCode() != 200)
| |
| {
| |
| send_message = "***Send failed. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message + "*** ";
| |
| return_message = "Send sticker failed. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
| |
| }
| |
|
| |
| send_message += "<sticker>" + sticker_id + "</sticker>(" + time + ")";
| |
| log_save(send_message, account_name_of_3ds, id);
| |
| return return_message;
| |
| }
| |
| | |
| function get_content_url(request_id, group_or_user_id, type)
| |
| {
| |
| var content_url = "https://api.line.me/v2/bot/message/" + request_id + "/content";
| |
| var url = "";
| |
| var folder_name;
| |
| var folder;
| |
| var sub_folder;
| |
| var folder_exist = false;
| |
| var sub_folder_exist = false;
| |
| | |
| try
| |
| {
| |
| if(type == "image")
| |
| folder_name = "Line_images";
| |
| else if(type == "audio")
| |
| folder_name = "Line_audio";
| |
| else if(type == "video")
| |
| folder_name = "Line_videos";
| |
|
| |
| var exist_folders = DriveApp.searchFolders("'me' in owners");
| |
| while (exist_folders.hasNext())
| |
| {
| |
| folder = exist_folders.next();
| |
| if(folder_name == folder.getName())
| |
| {
| |
| folder_exist = true;
| |
| break;
| |
| }
| |
| }
| |
|
| |
| if(!folder_exist)
| |
| folder = DriveApp.createFolder(folder_name);
| |
|
| |
|
| |
| var exist_sub_folders = folder.searchFolders("'me' in owners");
| |
| while (exist_sub_folders.hasNext())
| |
| {
| |
| sub_folder = exist_sub_folders.next();
| |
| if(group_or_user_id == sub_folder.getName())
| |
| {
| |
| sub_folder_exist = true;
| |
| break;
| |
| }
| |
| }
| |
|
| |
| if(!sub_folder_exist)
| |
| sub_folder = folder.createFolder(group_or_user_id);
| |
|
| |
| var response = UrlFetchApp.fetch(content_url,
| |
| {
| |
| 'headers': {
| |
| 'Content-Type': 'application/json; charset=UTF-8',
| |
| 'Authorization': 'Bearer ' + ACCESS_TOKEN,
| |
| }
| |
| });
| |
|
| |
| if(type == "image")
| |
| {
| |
| var image_data = response.getBlob().getAs("image/jpeg").setName(request_id + ".jpg");
| |
| var image = sub_folder.createFile(image_data);
| |
| image.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
| |
| url = "https://drive.google.com/uc?export=download&id=" + image.getId();
| |
| }
| |
| else if(type == "audio")
| |
| {
| |
| var audio_data = response.getBlob().setName(request_id + ".mp3");
| |
| var audio = sub_folder.createFile(audio_data);
| |
| audio.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
| |
| url = "https://drive.google.com/uc?export=download&id=" + audio.getId();
| |
| }
| |
| else if(type == "video")
| |
| {
| |
| var video_data = response.getBlob().setName(request_id + ".mp4");
| |
| var video = sub_folder.createFile(video_data);
| |
| video.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
| |
| url = "https://drive.google.com/uc?export=download&id=" + video.getId();
| |
| }
| |
| }
| |
| catch(error)
| |
| {
| |
| if(type == "image")
| |
| url = "Generate image url failed.";
| |
| else if(type == "audio")
| |
| url = "Generate audio url failed.";
| |
| else if(type == "video")
| |
| url = "Generate video url failed.";
| |
| }
| |
| return url;
| |
| }
| |
| | |
| | |
| function receive_msg_from_line(user_message, user_id, group_id, replyToken, time)
| |
| {
| |
| var url_reply = 'https://api.line.me/v2/bot/message/reply';
| |
| var url_profile = 'https://api.line.me/v2/bot/profile/';
| |
| if(user_message == "getid" || user_message == "getgroupid")
| |
| {
| |
| var send_id;
| |
| if(user_message == "getid")
| |
| send_id = user_id;
| |
| else if(user_message == "getgroupid")
| |
| send_id = group_id;
| |
|
| |
| UrlFetchApp.fetch(url_reply, {
| |
| 'headers': {
| |
| 'Content-Type': 'application/json; charset=UTF-8',
| |
| 'Authorization': 'Bearer ' + ACCESS_TOKEN,
| |
| },
| |
| 'method': 'post',
| |
| 'payload': JSON.stringify({
| |
| 'replyToken': replyToken,
| |
| 'messages': [{
| |
| 'type': 'text',
| |
| 'text': send_id ,
| |
| }],
| |
| 'notificationDisabled': 'true',
| |
| }),
| |
| });
| |
| log_save(send_id, "BOT", "IDs");
| |
| return;
| |
| }
| |
| | |
| try
| |
| {
| |
| var response = UrlFetchApp.fetch(url_profile + user_id, {
| |
| 'headers': {
| |
| 'Content-Type': 'application/json; charset=UTF-8',
| |
| 'Authorization': 'Bearer ' + ACCESS_TOKEN,
| |
| },
| |
| });
| |
| var profile_name = JSON.parse(response).displayName;
| |
| }
| |
| catch(error)
| |
| {
| |
| profile_name = "Unknown";
| |
| }
| |
| | |
| user_message += "(" + time + ")";
| |
| | |
| if(group_id == "Unknown")
| |
| log_save(user_message, profile_name, user_id);
| |
| else
| |
| log_save(user_message, profile_name, group_id);
| |
| }
| |
| | |
| function doPost(post_data)
| |
| {
| |
| var date = new Date();
| |
| var time = Utilities.formatDate( date, 'Asia/Tokyo', 'MM/dd hh:mm:ss');
| |
| var type = JSON.parse(post_data.postData.contents).type;
| |
| var result;
| |
|
| |
|
| if(type == undefined)
| |
| {
| |
| var msg_id = JSON.parse(post_data.postData.contents).events[0].message.id;
| |
| var user_message = JSON.parse(post_data.postData.contents).events[0].message.text;
| |
| var user_id = JSON.parse(post_data.postData.contents).events[0].source.userId;
| |
| var group_id = JSON.parse(post_data.postData.contents).events[0].source.groupId;
| |
| var replyToken = JSON.parse(post_data.postData.contents).events[0].replyToken;
| |
| var type = JSON.parse(post_data.postData.contents).events[0].message.type;
| |
|
| |
| if(user_id == undefined)
| |
| user_id = "Unknown";
| |
| if(group_id == undefined)
| |
| group_id = "Unknown";
| |
|
| |
|
| if(type == "sticker")
| |
| {
| |
| var sticker_sticker_id = JSON.parse(post_data.postData.contents).events[0].message.stickerId;
| |
| user_message = "<sticker>" + sticker_sticker_id + "</sticker>";
| |
| }
| |
| else if(type == "image")
| |
| {
| |
| if(group_id == "Unknown")
| |
| user_message = "<image_url>" + get_content_url(msg_id, user_id, type) + "</image_url>";
| |
| else
| |
| user_message = "<image_url>" + get_content_url(msg_id, group_id, type) + "</image_url>";
| |
| }
| |
| else if(type == "audio")
| |
| {
| |
| if(group_id == "Unknown")
| |
| user_message = "<audio_url>" + get_content_url(msg_id, user_id, type) + "</audio_url>";
| |
| else
| |
| user_message = "<audio_url>" + get_content_url(msg_id, group_id, type) + "</audio_url>";
| |
| }
| |
| else if(type == "video")
| |
| {
| |
| if(group_id == "Unknown")
| |
| user_message = "<video_url>" + get_content_url(msg_id, user_id, type) + "</video_url>";
| |
| else
| |
| user_message = "<video_url>" + get_content_url(msg_id, group_id, type) + "</video_url>";
| |
| }
| |
| else if(user_message == undefined)
| |
| user_message += " : " + type + " " + msg_id;
| |
|
| |
|
| receive_msg_from_line(user_message, user_id, group_id, replyToken, time);
| |
| return;
| |
| }
| |
|
| |
| var client_auth = JSON.parse(post_data.postData.contents).auth;
| |
| var client_gas_ver = JSON.parse(post_data.postData.contents).gas_ver;
| |
| if(client_auth == undefined)
| |
| client_auth = "";
| |
| if(client_gas_ver == undefined)
| |
| client_gas_ver = -1;
| |
|
| |
| if(script_password == client_auth)
| |
| {
| |
| if(gas_ver == client_gas_ver)
| |
| {
| |
| if(type == "send_text")
| |
| {
| |
| var id = JSON.parse(post_data.postData.contents).id;
| |
| var send_message = JSON.parse(post_data.postData.contents).message;
| |
| result = send_msg(id, send_message, time);
| |
| }
| |
| else if(type == "send_sticker")
| |
| {
| |
| var id = JSON.parse(post_data.postData.contents).id;
| |
| var package_id = JSON.parse(post_data.postData.contents).package_id;
| |
| var sticker_id = JSON.parse(post_data.postData.contents).sticker_id;
| |
| result = send_sticker(id, package_id, sticker_id, time);
| |
| }
| |
| else
| |
| result = "Unknown message type.";
| |
| }
| |
| else
| |
| result = "Google Apps Script version does not match. Server's GAS\nver is " + gas_ver + ", but 3DS's GAS ver is " + client_gas_ver + ". Please use the same version.";
| |
| }
| |
| else
| |
| result = "Auth failed. Please set correct password.";
| |
|
| |
| return ContentService.createTextOutput(result);
| |
| }
| |
|
| |
|
| function doGet(post_data)
| |
| {
| |
| var data = "";
| |
| var client_auth = post_data.parameter.script_auth;
| |
| var client_gas_ver = post_data.parameter.gas_ver;
| |
| if(client_auth == undefined)
| |
| client_auth = "";
| |
| if(client_gas_ver == undefined)
| |
| client_gas_ver = -1;
| |
|
| |
| if(script_password == client_auth)
| |
| {
| |
| if(gas_ver == client_gas_ver)
| |
| {
| |
| data = log_read(post_data.parameter.id);
| |
| data += "<success>";
| |
| }
| |
| else
| |
| data = "Google Apps Script version does not match. Server's GAS\nver is " + gas_ver + ", but 3DS's GAS ver is " + client_gas_ver + ". Please use the same version.";
| |
| }
| |
| else
| |
| data = "Auth failed. Please set correct password.";
| |
|
| |
| return ContentService.createTextOutput(data);
| |
| }</pre>
| |
|
| |
|
|
| |
|
Line 554: |
Line 132: |
| ''''''<span style="color: rgb(64, 64, 64)"><span style="font-size: 15px">And turn on </span></span><span style="color: rgb(0, 0, 255)"><span style="font-size: 15px">'''Use webhook'''</span></span>''''''<br /> | | ''''''<span style="color: rgb(64, 64, 64)"><span style="font-size: 15px">And turn on </span></span><span style="color: rgb(0, 0, 255)"><span style="font-size: 15px">'''Use webhook'''</span></span>''''''<br /> |
| https://dlhb.gamebrew.org/3dshomebrew/5dc4172fb531eacaf26757aa3fdb1b64.png | | https://dlhb.gamebrew.org/3dshomebrew/5dc4172fb531eacaf26757aa3fdb1b64.png |
| | |
| | |
| | == Patch note == |
| | |
| | 【App】 Ver 1.7.2 |
| | *【Line】 Fixed some bugs. |
| | *【Vid】 Added debug infomation. |
| | *【Vid】 Changed initial image size and position |
| | *【App】 Some minor update to better user experience. |
| | |
| | 【App】 Ver 1.7.1 |
| | *【Line】 Added- Supported download all files. |
| | *【Mup/Vid】 Added- Additional formats support(.ogg). |
| | *【App】 Some minor update to better user experience. |
| | |
| | 【App】 Ver 1.7.0 |
| | *【App】 Fixed- Fonts was improved. |
| | *【App】 Added- Video player. |
| | *【Line】 Added- Now, you can play audio and video sent by user. |
| | *【Cam】 Fixed- Framerate was improved. |
| | *【Mup】 Added- Additional formats (like aac) are supported. |
| | *【App】 Some minor update to better user experience. |
| | |
| | V1.3.1 |
| | * 【App】 Ver 1.3.1 |
| | * 【Line】 Fixed- Common occurring [FSUSER_OpenArchive failed] error has been solved. |
| | * 【Line】 Fixed- Image display now activated by touching the message. |
| | * 【Setting】 Fixed- Setting menu framerate now improved. |
| | * 【Image viewer】 Fixed- Image display speed now improved. |
| | |
| | v1.2.0 |
| | * Fix- Some crashes |
| | * Fix- Some settings do not work |
| | * Fix- Log download (GAS processing) speed has been improved(GAS update required) |
| | * Add- Image viewer |