Toggle menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Line for 3DS: Difference between revisions

From GameBrew
No edit summary
m (Text replacement - "Category:Other 3DS homebrew applications" to "")
 
(10 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{Infobox 3DS Homebrews
{{Infobox 3DS Homebrews
|title=Line for 3DS
|title=Line for 3DS
|image=Line-for-3DS.png
|image=Linefor3ds2.png
|description=Line for 3DS
|description=Line for 3DS.
|author=Core-2-Extreme
|author=Core-2-Extreme
|lastupdated=2021/01/07
|lastupdated=2024/01/30
|type=Other Apps
|type=Other Apps
|version=1.7.2
|version=2.0.0.1
|license=Mixed
|license=GPL-3.0
|download=https://dlhb.gamebrew.org/3dshomebrew/Line_for_3DSv1.7.2.7z
|download=https://github.com/Core-2-Extreme/Line_for_3DS/releases
|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
|source=https://github.com/Core-2-Extreme/Line_for_3DS
|source=https://github.com/Core-2-Extreme/Line_for_3DS
|donation=https://github.com/Core-2-Extreme/Line_for_3DS/releases
}}
}}
<youtube>5K2fCr0lyoM</youtube>
Line for 3DS is a LINE client for the 3DS. Developed by LINE, it is an instant messaging, voice and video call application. It allows you to exchange text messages and make calls free of charge by transmitting information in the form of data.


== Line for 3DS ==
Setup instructions can be found [https://github.com/Core-2-Extreme/Line_for_3DS/blob/v2.0.0/SETUP.md here].


https://dlhb.gamebrew.org/3dshomebrew/77541203-a9977e80-6ee7-11ea-8479-53aed389ab64.png
==Features==
* Send text.
* Send stickers. Only package ID 11537, 11538 and 11539 in sticker definitions are supported for now.
* Send pictures, Up to 37.5MB per picture.
* Send videos, Up to 37.5MB per video.
* Send audio, Up to 37.5MB per audio.
* Send files, Up to 37.5MB per file.
* View old chat logs, Up to 4000 chat logs.
* Receive text.
* Receive stickers. Only package ID 11537, 11538 and 11539 in sticker definitions are displayed for now.
* Receive pictures. Very large images may not be displayed especially on OLD3DS.
* Receive audio.
* Receive videos. Some videos may not be played at full speed or at all especially on OLD3DS.
* Direct message.
* Group chat. If Allow bot to join group chat feature in LINE developer page (see Create your LINE BOT step 6) is enabled.
* Open chat.
* Voice call.
* Night mode.


==Supported function:==
==Screenshots==
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds3.png
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds4.png


* Send text ✅
==Media==
* Send image ✅ (v1.4.0)
'''Line for 3DS''' ([https://www.youtube.com/watch?v=5K2fCr0lyoM Line for 3DS]) <br>
* Send video ✅(*2) (v1.4.0)
<youtube>5K2fCr0lyoM</youtube>
* 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 ==
 
[https://discord.gg/EqK3Kpb https://discord.gg/EqK3Kpb]
 
== How to use ==
 
'''*1 The stickers must be included this list'''
[https://dlhb.gamebrew.org/3dshomebrew/sticker_list.pdf sticker_list.pdf]
 
'''*2 Google drive URL will be sent(not embed).'''
 
===How to setup Google apps script===
<div class="toccolours mw-collapsible mw-collapsed">
Please click here to see the contents:
<div class="mw-collapsible-content">
'''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 />
'''Click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">log in.</span></span>'''<br />
Click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Log in with LINE account.</span></span>'''<br />
Click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Create.</span></span>'''''''''<br />
https://dlhb.gamebrew.org/3dshomebrew/43147207b4eb4e1684a075e305cb67a2.png<br />
''''''Type your name and click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Create.</span></span>'''''''''<br />
https://dlhb.gamebrew.org/3dshomebrew/9cc90ea609fef8352e3bca436c3dfadb.png<br />
''''''Click '''<span style="color: rgb(0, 0, 255)"><span style="font-size: 15px">Create a Messaging API channel</span>.</span>'''''''''<br />
https://dlhb.gamebrew.org/3dshomebrew/2394c4de74d3c34c186f373a02b3f2f6.png<br />
'''<br />
'''Type each item and click <span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">'''Create.'''</span></span>''''''<br />
https://dlhb.gamebrew.org/3dshomebrew/e611043d9c7da0be833f17bd36e62bc5.png<br />
''''''Click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Messaging API.</span></span>''''''<br />
'''<br />
https://dlhb.gamebrew.org/3dshomebrew/73774f8d71e0ca5756e72fda20f94ebd.png<br />
'''<br />
'''Find '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Channel access token (long-lived)</span></span>''' and click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Issue.</span></span>'''<br />
Then copy '''<span style="color: rgb(0, 0, 255)"><span style="font-size: 15px">Channel access token (long-lived).</span></span>'''''''''<br />
https://dlhb.gamebrew.org/3dshomebrew/15ffd7c288cbf6ab5144158f5402e3a5.png<br />
'''<br />
Please access this site.<br />
'''[https://www.google.com/intl/en_ALL/drive/ <span style="font-size: 15px"><span style="color: rgb(0, 0, 0)">https://www.google.com/intl/en_ALL/drive/</span></span>]<br />
Click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Go to google drive</span></span>''' and '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">login.</span></span>'''<br />
Click '''<span style="font-size: 15px"><span style="color: rgb(77, 77, 255)">New </span></span>'''and click'''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)"> Google sheets(1 time) </span></span>'''and click <span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">'''More''' </span><span style="color: rgb(0, 0, 0)">'''-&gt;'''</span><span style="color: rgb(0, 0, 255)"> '''Google apps script (1 time).'''</span></span>''''''<br />
https://dlhb.gamebrew.org/3dshomebrew/5a603a93e5c14227b22e3bd661c43016.png<br />
'''<br />
<br />
'''Open created <span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">'''Google sheets''' </span><span style="color: rgb(64, 64, 64)">and copy</span> <span style="color: rgb(0, 0, 255)">'''sheet id.(Do''' </span><span style="color: rgb(255, 0, 0)">'''NOT''' </span><span style="color: rgb(0, 0, 255)">'''include ''''</span><span style="color: rgb(255, 0, 0)">'''/'''</span><span style="color: rgb(0, 0, 255)">'''' )'''</span></span>''''''<br />
https://dlhb.gamebrew.org/3dshomebrew/f22b2c7b64acf04808a118983f744685.png<br />
''''''<span style="color: rgb(0, 0, 255)"><br />
</span>'''<br />
Open created '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Google apps script </span></span>'''and Insert name.(&quot;Line main&quot; in this guide)'''<br />
https://dlhb.gamebrew.org/3dshomebrew/cf714d96090d736cb42a24b7a8997deb.png<br />
'''<br />
'''Copy this code to &quot;<span style="font-size: 15px">Line main</span>&quot; and add your <span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">sheet ID, </span></span>'''<span style="font-size: 15px">'''<span style="color: rgb(0, 0, 255)">access token</span>''' and '''<span style="color: rgb(0, 0, 255)"> 3DS's account name</span>(Your favorite name).'''</span><br />
'''<br />
 
<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 code compatible with ver 1.7.0~! 2021/01/24 Edited : fixed unable to receive images, audio, videos and files. </p>
<syntaxhighlight lang="C++" style="border: 3px dashed blue;">
var ACCESS_TOKEN = "Your acces token here";
var OPEN_SHEET_ID = "Your sheet id here";
var SCRIPT_PASSOWRD = "Your google apps script password here";
var CLIENT_NAME = "Your 3ds's account name here";
 
var gas_ver = 7;//Do **NOT** edit this value.
function log_save(message, user_name, write_sheet_name, group_or_user_name, pic_url, msg_id)
{
    var google_pic_url;
    var previous_pic_url;
    var response;
    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);
            write_sheet.getRange("C" + sheet_pos).setValue(msg_id);         
            break;
        }
        else
            sheet_pos++;
    }
 
    write_cache_data(write_sheet, (sheet_pos + 1), "B1");
    if (group_or_user_name != "Do not save")
        write_cache_data(write_sheet, group_or_user_name, "B2");
 
    if (pic_url != "Do not save")
    {
        previous_pic_url = get_previous_pic_url(write_sheet);
        if (previous_pic_url != pic_url)
        {
            response = UrlFetchApp.fetch(pic_url,
                {
                    'headers': {
                        'Content-Type': 'application/json; charset=UTF-8',
                        'Authorization': 'Bearer ' + ACCESS_TOKEN,
                    },
                    muteHttpExceptions: true,
                });
 
            if (response.getResponseCode() == 200)
            {
                google_pic_url = get_profile_pic_google_url(response, write_sheet_name);
                write_cache_data(write_sheet, google_pic_url, "B3");
                write_cache_data(write_sheet, pic_url, "B4");
            }
        }
    }
}
 
function unsend_msg(sheet_id, msg_id)
{
    var spreadsheet = SpreadsheetApp.openById(OPEN_SHEET_ID);
    var write_sheet = spreadsheet.getSheetByName(sheet_id);
    var sheet_pos = 1;
 
    if (!write_sheet)
    {
        spreadsheet.insertSheet(sheet_id);
        write_sheet = spreadsheet.getSheetByName(sheet_id);
    }
 
    while (true)
    {
        var sheet_data = write_sheet.getRange("C" + sheet_pos).getValue();
 
        if (sheet_data == msg_id)
        {
            write_sheet.getRange("A" + sheet_pos).setValue("*****This message was unsent by user*****");
            break;
        }
        else
            sheet_pos++;
    }
}
 
function check_folder_exist(input_folders, name)
{
    var folder;
    while (input_folders.hasNext())
    {
      folder = input_folders.next();
      if(name == folder.getName())
        return folder;
    }
    return undefined;
}
 
function log_read(id, num_of_logs)
{
  var sheet_offset = 1;
  var sheet_start = 1;
  var return_data;
  var sheet_data;
  var spreadsheet = SpreadsheetApp.openById(OPEN_SHEET_ID);
  var read_sheet = spreadsheet.getSheetByName(id);
  if(!read_sheet)
    return "No such sheet.\nPlease duoble check your ID.";
 
  sheet_start = get_cache_pos(read_sheet);
 
  while(true)
  {
    sheet_data = read_sheet.getRange("A" + sheet_start).getValue();
    if(sheet_data == "")
    {
      write_cache_data(read_sheet, sheet_start, "B1");
      if(sheet_start > num_of_logs)
        sheet_start = (sheet_start - num_of_logs);
      else
        sheet_start = 1;
 
      break;
    }
    else
      sheet_start++;
  }
 
  sheet_offset = sheet_start;
  sheet_data = read_sheet.getRange(sheet_start, 1, num_of_logs).getValues();
  return_data = "<0>";
  for(var i = 0; i < num_of_logs; i++)
  {
    if(sheet_data[i] == "")
      break;
 
    return_data += sheet_data[i];
    sheet_offset++;
  }
  return_data += "</0>";
  return_data += "<1>" + get_cache_name(read_sheet) + "</1>";
  return_data += "<2>" + get_google_pic_url(read_sheet) + "</2>";
  return_data += "<3>" + sheet_offset + "</3>";
  return_data += "<4>Success</4>";
  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 get_cache_name(sheet_object)
{
  var cached_name = sheet_object.getRange("B2").getValue();
  return cached_name;
}
 
function get_google_pic_url(sheet_object)
{
    var cached_name = sheet_object.getRange("B3").getValue();
    return cached_name;
}
 
function get_previous_pic_url(sheet_object)
{
    var cached_name = sheet_object.getRange("B4").getValue();
    return cached_name;
}
 
function write_cache_data(sheet_object, cache_data, pos)
{
  sheet_object.getRange(pos).setValue(cache_data);
}
 
function http_get_content(request_id)
{
  var response;
  var url = "https://api-data.line.me/v2/bot/message/" + request_id + "/content";
  response = UrlFetchApp.fetch(url,
  {
  'headers': {
    'Content-Type': 'application/json; charset=UTF-8',
    'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_get_user_name(id)
{
  var response;
  var url = 'https://api.line.me/v2/bot/profile/' + id;
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_get_group_user_name(id, group_id)
{
  var response;
  var url = "https://api.line.me/v2/bot/group/" + group_id + "/member/" + id + "/";
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_get_group_name(group_id)
{
  var response;
  var url = "https://api.line.me/v2/bot/group/" + group_id +"/summary/";
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_send_text_reply(reply_token, send_msg)
{
  var response;
  var url = 'https://api.line.me/v2/bot/message/reply/';
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': reply_token,
      'messages': [{
        'type': 'text',
        'text': send_msg ,
      }],
      'notificationDisabled': 'true',
    }),
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_send_text(id, send_msg)
{
  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_msg,
          "type": "text",
      }],
      'notificationDisabled': 'false',
    }),
  muteHttpExceptions: true,
  });
  return response;
}
 
function http_send_sticker(id, package_id, sticker_id)
{
  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,
  });
  return response;
}
 
function http_send_image(id, img_url, preview_url)
{
  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": "image",
          "originalContentUrl": img_url,
          "previewImageUrl": preview_url,
      }],
      'notificationDisabled': 'false',
    }),
  muteHttpExceptions: true,
  });
  return response;
}
 
function send_msg(id, send_message, time)
{
  var cache;
  var response;
  var return_message = "Success";
 
  response = http_send_text(id, send_message);
  if(response.getResponseCode() != 200)
  {
    cache = "***The message failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message + "*** ";
    cache += send_message;
    send_message = cache;
    return_message = "The message failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
  }
  send_message += "(" + time + ")";
  log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save", "0");
  return return_message;
}
 
function send_sticker(id, package_id, sticker_id, time)
{
  var response;
  var return_message = "Success";
  var send_message = "";
 
  response = http_send_sticker(id, package_id, sticker_id);
  if(response.getResponseCode() != 200)
  {
  send_message = "***The sticker failed to send. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
  return_message = "The sticker failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
  }
  send_message += "<sticker>" + sticker_id + "</sticker>(" + time + ")";
  log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save", "0");
  return return_message;
}
 
function get_cache_data(id, cache_name, last_data)
{
  var byte_data;
  var cache_data = "";
  var cache_data_1 = "";
  var cache_file;
  var cache_folder;
  var exist_cache_folders;
  var exist_folders;
  var exist_sub_folders;
  var folder;
  var folder_name = "Line_contents";
  var sub_folder;
 
  exist_folders = DriveApp.searchFolders("'me' in owners");
  folder = check_folder_exist(exist_folders, folder_name);
  if(folder == undefined)
    return undefined;
 
  exist_sub_folders = folder.searchFolders("'me' in owners");
  sub_folder = check_folder_exist(exist_sub_folders, id);
  if(sub_folder == undefined)
    return undefined;
 
  exist_cache_folders = sub_folder.searchFolders("'me' in owners");
  cache_folder = check_folder_exist(exist_cache_folders, cache_name);
  if(cache_folder == undefined)
    return undefined;
 
  for(var i = 0; i < 4096; i++)
  {
    try
    {
      cache_file = cache_folder.getFilesByName(i).next();
      cache_data += cache_file.getBlob().getDataAsString();
    }
    catch(error)
    {
      break;
    }
  }
 
  cache_data += last_data;
  cache_folder.setTrashed(true);
  return cache_data;
}
 
function upload_content(id, data, count, cache_name, last, time, data_type)
{
  var cache_data;
  var cache_folder;
  var exist_cache_folders;
  var exist_folders;
  var exist_sub_folders;
  var folder;
  var folder_name = "Line_contents";
  var part_of_content;
  var part_of_content_data;
  var return_message = "Success";
  var send_message;
  var sub_folder;
 
  if(count == 0 && last == "true")
    return_message = send_content(id, cache_name, time, data, data_type);
  else if(last == "true")
  {
      cache_data = get_cache_data(id, cache_name, data);
      if(cache_data == undefined)
      {
        send_message = "***The content failed to send. The cache folder does not exist.*** (" + time + ")";
        log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save", "0");
        return_message = "The content failed to send.\nThe cache folder does not exist.";
      }
      else
        return_message = send_content(id, cache_name, time, cache_data, data_type);
  }
  else
  {
    exist_folders = DriveApp.searchFolders("'me' in owners");
    folder = check_folder_exist(exist_folders, folder_name);
    if(folder == undefined)
      folder = DriveApp.createFolder(folder_name);
 
    exist_sub_folders = folder.searchFolders("'me' in owners");
    sub_folder = check_folder_exist(exist_sub_folders, id);
    if(sub_folder == undefined)
      sub_folder = folder.createFolder(id);
 
    exist_cache_folders = sub_folder.searchFolders("'me' in owners");
    cache_folder = check_folder_exist(exist_cache_folders, cache_name);
    if(cache_folder == undefined)
      cache_folder = sub_folder.createFolder(cache_name);
    else if(!(cache_folder == undefined) && count == 0)
    {
      cache_folder.setTrashed(true);
      cache_folder = sub_folder.createFolder(cache_name);
    }
 
    part_of_content_data = Utilities.newBlob(data).setName(count);
    part_of_content = cache_folder.createFile(part_of_content_data);
  }
  return return_message;
}
 
function send_content(id, cache_name, time, encoded_data, data_type)
{
  var content;
  var content_data;
  var content_type;
  var content_url = "";
  var exist_folders;
  var exist_sub_folders;
  var folder;
  var folder_name = "Line_contents";
  var preview_content_url = "";
  var response;
  var return_message = "Success";
  var send_message = "";
  var sub_folder;
 
  exist_folders = DriveApp.searchFolders("'me' in owners");
  folder = check_folder_exist(exist_folders, folder_name);
  if(folder == undefined)
    folder = DriveApp.createFolder(folder_name);
 
  exist_sub_folders = folder.searchFolders("'me' in owners");
  sub_folder = check_folder_exist(exist_sub_folders, id);
  if(sub_folder == undefined)
    sub_folder = folder.createFolder(id);
 
  try
  {
    content_data = Utilities.newBlob(Utilities.base64Decode(encoded_data)).setName(cache_name);
  }
  catch (e)
  {
    return_message = "The content failed to send.\nCouldn't decode base64 data.";
    return return_message;
  }
 
  content = sub_folder.createFile(content_data);
  content_type = content.getMimeType();
  if(content_type == MimeType.BMP || content_type == MimeType.GIF || content_type == MimeType.JPEG || content_type == MimeType.PNG || content_type == MimeType.SVG)
  {
    content.setTrashed(true);
    folder_name = "Line_images";
    exist_folders = DriveApp.searchFolders("'me' in owners");
    folder = check_folder_exist(exist_folders, folder_name);
    if(folder == undefined)
      folder = DriveApp.createFolder(folder_name);
 
    exist_sub_folders = folder.searchFolders("'me' in owners");
    sub_folder = check_folder_exist(exist_sub_folders, id);
    if(sub_folder == undefined)
      sub_folder = folder.createFolder(id);
 
    content = sub_folder.createFile(content_data);
    content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
    content_url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w16383-h16383";
    preview_content_url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w240-h240";
 
    response = http_send_image(id, content_url, preview_content_url);
    if(response.getResponseCode() != 200)
    {
      send_message = "***The image failed to send. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
      return_message = "The image failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
    }
    send_message += "<image_url>" + content_url + "</image_url>(" + time + ")";
    log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save", "0");
    return return_message;
  }
  else if(data_type == "audio")
  {
    content.setTrashed(true);
    folder_name = "Line_audio";
    exist_folders = DriveApp.searchFolders("'me' in owners");
    folder = check_folder_exist(exist_folders, folder_name);
    if(folder == undefined)
      folder = DriveApp.createFolder(folder_name);
 
    exist_sub_folders = folder.searchFolders("'me' in owners");
    sub_folder = check_folder_exist(exist_sub_folders, id);
    if(sub_folder == undefined)
      sub_folder = folder.createFolder(id);
 
    content = sub_folder.createFile(content_data);
    content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
    content_url = "https://docs.google.com/uc?id=" + content.getId();
    response = http_send_text(id, content_url + " (audio)");
    if(response.getResponseCode() != 200)
    {
      send_message = "***The audio failed to send. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
      return_message = "The audio failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
    }
 
    content_url = "https://drive.google.com/uc?export=download&id=" + content.getId();
    send_message += "<audio_url>" + content_url + "</audio_url>(" + time + ")";
    log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save", "0");
    return return_message;
  }
  else if(data_type == "video")
  {
    content.setTrashed(true);
    folder_name = "Line_videos";
    exist_folders = DriveApp.searchFolders("'me' in owners");
    folder = check_folder_exist(exist_folders, folder_name);
    if(folder == undefined)
      folder = DriveApp.createFolder(folder_name);
 
    exist_sub_folders = folder.searchFolders("'me' in owners");
    sub_folder = check_folder_exist(exist_sub_folders, id);
    if(sub_folder == undefined)
      sub_folder = folder.createFolder(id);
 
    content = sub_folder.createFile(content_data);
    content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
    content_url = "https://docs.google.com/uc?id=" + content.getId();
    response = http_send_text(id, content_url);
    if(response.getResponseCode() != 200)
    {
      send_message = "***The video failed to send. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
      return_message = "The video failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
    }
 
    content_url = "https://drive.google.com/uc?export=download&id=" + content.getId();
    send_message += "<video_url>" + content_url + "</video_url>(" + time + ")";
    log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save", "0");
    return return_message;
  }
  else
  {
    content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
    content_url = "https://drive.google.com/file/d/" + content.getId() + "/view";
    response = http_send_text(id, content_url);
    if(response.getResponseCode() != 200)
    {
      send_message = "***The content failed to send. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
      return_message = "The content failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
    }
    content_url = "https://drive.google.com/uc?export=download&id=" + content.getId();
    send_message += "<content_url>" + content_url + "</content_url>(" + time + ")";
    log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save", "0");
    return return_message;
  }
}
 
function get_profile_pic_google_url(http_response, group_or_user_id)
{
    var content_data;
    var content;
    var folder_name = "Line_images";
    var folder;
    var sub_folder;
    var exist_folders;
    var exist_sub_folders;
    var url = "";
 
    exist_folders = DriveApp.searchFolders("'me' in owners");
    folder = check_folder_exist(exist_folders, folder_name);
    if (folder == undefined)
        folder = DriveApp.createFolder(folder_name);
 
    exist_sub_folders = folder.searchFolders("'me' in owners");
    sub_folder = check_folder_exist(exist_sub_folders, group_or_user_id);
    if (sub_folder == undefined)
        sub_folder = folder.createFolder(group_or_user_id);
 
    content_data = http_response.getBlob().getAs("image/jpeg").setName("icon");
    content = sub_folder.createFile(content_data);
    content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
    url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w32-h32";
 
    return url;
}
 
function get_content_url(request_id, group_or_user_id, type)
{
  var content_data;
  var content;
  var exist_folders;
  var exist_sub_folders;
  var folder_name;
  var folder;
  var sub_folder;
  var response;
  var url = "";
 
  if(type == "image")
    folder_name = "Line_images";
  else if(type == "audio")
    folder_name = "Line_audio";
  else if(type == "video")
    folder_name = "Line_videos";
  else if(type == "file")
    folder_name = "Line_contens";
 
 
  exist_folders = DriveApp.searchFolders("'me' in owners");
  folder = check_folder_exist(exist_folders, folder_name);
  if(folder  == undefined)
    folder = DriveApp.createFolder(folder_name);
 
  exist_sub_folders = folder.searchFolders("'me' in owners");
  sub_folder = check_folder_exist(exist_sub_folders, group_or_user_id);
  if(sub_folder  == undefined)
    sub_folder = folder.createFolder(group_or_user_id);
 
  response = http_get_content(request_id);
  if(response.getResponseCode() != 200)
  {
    if(type == "image")
      url = "***Couldn't generate an image URL. Status code = " + response.getResponseCode() + "*** ";
    else if(type == "audio")
      url = "***Couldn't generate a audio URL. Status code = " + response.getResponseCode() + "*** ";
    else if(type == "video")
      url = "***Couldn't generate a video URL. Status code = " + response.getResponseCode() + "*** ";
    else if(type == "file")
      url = "***Couldn't generate a file URL. Status code = " + response.getResponseCode() + "*** ";
  }
  else
  {
    if(type == "image")
    {
      content_data = response.getBlob().getAs("image/jpeg").setName(request_id);
      content = sub_folder.createFile(content_data);
      content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
      url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w16383-h16383";
    }
    else if(type == "audio" || type == "video" || type == "file")
    {
      content_data = response.getBlob().setName(request_id);
      content = sub_folder.createFile(content_data);
      content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
      url = "https://drive.google.com/uc?export=download&id=" + content.getId();
    }
  }
  return url;
}
 
function receive_msg_from_line(user_message, user_id, group_id, reply_token, time, msg_id)
{
  var group_name;
  var picture_url = [ "", ""];
  var response = [ "", ""];
  var send_msg;
  var user_name;
 
  if(group_id == "Unknown")
    response[0] = http_get_user_name(user_id);
  else
  {
    response[0] = http_get_group_user_name(user_id, group_id);
    response[1] = http_get_group_name(group_id);
 
    if(response[1].getResponseCode() != 200)
      group_name = "Unknown";
    else
    {
      group_name = JSON.parse(response[1]).groupName;
      picture_url[1] = JSON.parse(response[1]).pictureUrl;
    }
  }
 
  if(response[0].getResponseCode() != 200)
    user_name = "Unknown";
  else
  {
    user_name = JSON.parse(response[0]).displayName;
    picture_url[0] = JSON.parse(response[0]).pictureUrl;
  }
 
  if(user_message == "getid" || user_message == "getgroupid")
  {
    if(user_message == "getid")
      send_msg = user_id;
    else if(user_message == "getgroupid")
      send_msg = group_id;
 
    response[0] = http_send_text_reply(reply_token, send_msg);
    send_msg = "<id>" + send_msg + "</id>";
    if(response[0].getResponseCode() != 200)
      send_msg += "***The message failed to send. Status code = " + response[0].getResponseCode() + " " + JSON.parse(response[0].getContentText()).message + "*** ";
 
    send_msg += "(" + time + ")";
    if(user_message == "getid")
    {
      log_save(send_msg, user_name, "IDs", "BOT", "Do not save", "0");
      user_message = "*****Added by user*****";
    }
    else if(user_message == "getgroupid")
    {
      log_save(send_msg, group_name, "IDs", "BOT", "Do not save", "0");
      user_message = "*****Invited by user*****";
    }
    user_name = CLIENT_NAME;
    msg_id = 0;
  }
  else if(user_message == "*****Removed by user*****" || user_message == "*****Blocked by user*****" || user_message == "*****Joined*****" || user_message == "*****Left*****")
  {
    msg_id = 0;
    if(user_message == "*****Removed by user*****" || user_message == "*****Blocked by user*****")
      user_name = CLIENT_NAME;
  }
  user_message += "(" + time + ")";
  if(group_id == "Unknown")
    log_save(user_message, user_name, user_id, user_name, picture_url[0], msg_id);
  else
    log_save(user_message, user_name, group_id, group_name, picture_url[1], msg_id);
}
 
function doPost(post_data)
{
  var time = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'MM/dd hh:mm:ss');
  var data_type;
  var result;
  var client_auth = "Unknown";
  var client_gas_ver = "Unknown";
  var id = "Unknown";
  var last = "";
  var lock = LockService.getScriptLock();
  var lock_result;
  var logs = 100;
  try
  {
    data_type = JSON.parse(post_data.postData.contents).type;
  }
  catch(e)
  {
  }
  for(var i = 0; i < 1000; i++)
  {
    lock_result = lock.tryLock(1000);
    if(lock_result)
      break;
    Utilities.sleep(1000);
  }
 
  if(data_type == undefined)
  {
    for(var i = 0; i < 50; i++)
    {
      var msg_id = "";
      var user_message = "";
      var user_id = "";
      var group_id = "";
      var reply_token = "";
      var msg_type = "";
      var type = "";
   
      try
      {
        type = JSON.parse(post_data.postData.contents).events[i].type;
        user_id = JSON.parse(post_data.postData.contents).events[i].source.userId;
        group_id = JSON.parse(post_data.postData.contents).events[i].source.groupId;
        reply_token = JSON.parse(post_data.postData.contents).events[i].replyToken;
        if(type == "unsend")
          msg_id = JSON.parse(post_data.postData.contents).events[i].unsend.messageId;
        else if(type == "message")
        {
          msg_id = JSON.parse(post_data.postData.contents).events[i].message.id;
          user_message = JSON.parse(post_data.postData.contents).events[i].message.text;
          msg_type = JSON.parse(post_data.postData.contents).events[i].message.type;               
        }
      }
      catch(e)
      {
        break;
      }
 
      if(user_id == undefined)
        user_id = "Unknown";
      if(group_id == undefined)
        group_id = "Unknown";
 
 
      if(type == "follow")
        user_message = "getid";
      else if(type == "join")
        user_message = "getgroupid";
      else if(type == "leave")
        user_message = "*****Removed by user*****";
      else if(type == "unfollow")
        user_message = "*****Blocked by user*****";
      else if(type == "memberJoined")
      {
        user_message = "*****Joined*****";
        user_id = JSON.parse(post_data.postData.contents).events[i].joined.members[0].userId;
      }
      else if(type == "memberLeft")
      {
        user_message = "*****Left*****";
        user_id = JSON.parse(post_data.postData.contents).events[i].left.members[0].userId;
      }
      else if(type == "unsend")
      {
        if(group_id == "Unknown")
          unsend_msg(user_id, msg_id);
        else
          unsend_msg(group_id, msg_id);
      }
      else if(msg_type == "sticker")
      {
        var sticker_id  = JSON.parse(post_data.postData.contents).events[i].message.stickerId;
        user_message = "<sticker>" + sticker_id + "</sticker>";
      }
      else if(msg_type == "image")
      {       
        if(group_id == "Unknown")
          user_message = "<image_url>" + get_content_url(msg_id, user_id, msg_type) + "</image_url>";
        else
          user_message = "<image_url>" + get_content_url(msg_id, group_id, msg_type) + "</image_url>";
      }
      else if(msg_type == "audio")
      {
      if(group_id == "Unknown")
        user_message = "<audio_url>" + get_content_url(msg_id, user_id, msg_type) + "</audio_url>";
      else
        user_message = "<audio_url>" + get_content_url(msg_id, group_id, msg_type) + "</audio_url>";
      }
      else if(msg_type == "video")
      {
      if(group_id == "Unknown")
        user_message = "<video_url>" + get_content_url(msg_id, user_id, msg_type) + "</video_url>";
      else
        user_message = "<video_url>" + get_content_url(msg_id, group_id, msg_type) + "</video_url>";
      }
      else if(msg_type == "file")
      {
      if(group_id == "Unknown")
        user_message = "<file_url>" + get_content_url(msg_id, user_id, msg_type) + "</file_url>";
      else
        user_message = "<file_url>" + get_content_url(msg_id, group_id, msg_type) + "</file_url>";
      }
      else if(user_message == undefined)
        user_message += " : " + msg_type + " " + msg_id;
 
      if(!(type == "unsend"))
        receive_msg_from_line(user_message, user_id, group_id, reply_token, time, msg_id);
    }
    lock.releaseLock();
    return;
  }
 
  try
  {
    client_auth = JSON.parse(post_data.postData.contents).auth;
    client_gas_ver = JSON.parse(post_data.postData.contents).gas_ver;
    id = JSON.parse(post_data.postData.contents).id;
  }
  catch(e)
  {
  }
 
  if(SCRIPT_PASSOWRD == client_auth)
  {
    if(gas_ver == client_gas_ver)
    {
      if(data_type == "send_text")
      {
        var send_message = JSON.parse(post_data.postData.contents).message;
        result = send_msg(id, send_message, time);
      }
      else if(data_type == "send_sticker")
      {
        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 if(data_type == "upload_content")
      {
        var content_data = JSON.parse(post_data.postData.contents).content_data;
        var count = JSON.parse(post_data.postData.contents).count;
        var name = JSON.parse(post_data.postData.contents).name;
        var sub_data_type = "";
        last = JSON.parse(post_data.postData.contents).last;
        if(last == "true")
          sub_data_type = JSON.parse(post_data.postData.contents).data_type;
     
        result = upload_content(id, content_data, count, name, last, time, sub_data_type);
      }
      else
        result = "Unknown message type.";
     
      if(result == "Success" && ((data_type == "upload_content" && last == "true") || data_type == "send_sticker" || data_type == "send_text"))
      {
        logs = JSON.parse(post_data.postData.contents).logs;
        result = log_read(id, logs);
      }
    }
    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.";
 
  lock.releaseLock();
  return ContentService.createTextOutput(result);
}
 
function doGet(post_data)
{
  var data = "";
  var client_auth = "";
  var client_gas_ver = "";
  var num_of_logs = "";
  var id = "";
  var lock = LockService.getScriptLock();
  var lock_result = lock.tryLock(1000);
  while(!lock_result)
  {
    Utilities.sleep(1000);
    lock_result = lock.tryLock(1000);
  }
  try
  {
    client_auth = post_data.parameter.script_auth;
    client_gas_ver = post_data.parameter.gas_ver;
    num_of_logs = post_data.parameter.logs;
    id = post_data.parameter.id;
  }
  catch(e)
  {
  }
  if(client_auth == undefined)
    client_auth = "";
  if(client_gas_ver == undefined)
    client_gas_ver = -1;
  if(num_of_logs == undefined)
    num_of_logs = 300;
  if(id == undefined)
    id = "unknown";
 
  if(SCRIPT_PASSOWRD == client_auth)
  {
    if(gas_ver == client_gas_ver)
      data = log_read(id, num_of_logs);
    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.";
 
  lock.releaseLock();
  return ContentService.createTextOutput(data);
}
 
</syntaxhighlight>
 
  </div>
  <div class="content" id="panel2">
    <p>This code compatible with ver 1.6.0 !!!WARNING!!! I strongly recommend you use the newest version !!!WARNING!!! </p>
<syntaxhighlight lang="C++" style="border: 3px dashed blue;">
var ACCESS_TOKEN = "Your acces token here";
var OPEN_SHEET_ID = "Your sheet id here";
var SCRIPT_PASSOWRD = "Your google apps script password here";
var CLIENT_NAME = "Your 3ds's account name here";
 
var gas_ver = 6;//Do **NOT** edit this value.
function log_save(message, user_name, write_sheet_name, group_or_user_name, pic_url)
{
    var google_pic_url;
    var previous_pic_url;
    var response;
    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_data(write_sheet, (sheet_pos + 1), "B1");
    if (group_or_user_name != "Do not save")
        write_cache_data(write_sheet, group_or_user_name, "B2");
 
    if (pic_url != "Do not save")
    {
        previous_pic_url = get_previous_pic_url(write_sheet);
        if (previous_pic_url != pic_url)
        {
            response = UrlFetchApp.fetch(pic_url,
                {
                    'headers': {
                        'Content-Type': 'application/json; charset=UTF-8',
                        'Authorization': 'Bearer ' + ACCESS_TOKEN,
                    },
                    muteHttpExceptions: true,
                });
 
            if (response.getResponseCode() == 200)
            {
                google_pic_url = get_profile_pic_google_url(response, write_sheet_name);
                write_cache_data(write_sheet, google_pic_url, "B3");
                write_cache_data(write_sheet, pic_url, "B4");
            }
        }
 
    }
}
 
function check_folder_exist(input_folders, name)
{
    var folder;
    while (input_folders.hasNext())
    {
      folder = input_folders.next();
      if(name == folder.getName())
        return folder;
    }
    return undefined;
}
 
function log_read(id, num_of_logs)
{
  var sheet_offset = 1;
  var sheet_start = 1;
  var return_data;
  var sheet_data;
  var spreadsheet = SpreadsheetApp.openById(OPEN_SHEET_ID);
  var read_sheet = spreadsheet.getSheetByName(id);
  if(!read_sheet)
    return "No such sheet.\nPlease duoble check your ID.";
  sheet_start = get_cache_pos(read_sheet);
 
  while(true)
  {
    sheet_data = read_sheet.getRange("A" + sheet_start).getValue();
    if(sheet_data == "")
    {
      write_cache_data(read_sheet, sheet_start, "B1");
      if(sheet_start > num_of_logs)
        sheet_start = (sheet_start - num_of_logs);
      else
        sheet_start = 1;
 
      break;
    }
    else
      sheet_start++;
  }
 
  sheet_offset = sheet_start;
  sheet_data = read_sheet.getRange(sheet_start, 1, num_of_logs).getValues();
  return_data = "<0>";
  for(var i = 0; i < num_of_logs; i++)
  {
    if(sheet_data[i] == "")
      break;
 
    return_data += sheet_data[i];
    sheet_offset++;
  }
  return_data += "</0>";
  return_data += "<1>" + get_cache_name(read_sheet) + "</1>";
  return_data += "<2>" + get_google_pic_url(read_sheet) + "</2>";
  return_data += "<3>" + sheet_offset + "</3>";
  return_data += "<4>Success</4>";
  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 get_cache_name(sheet_object)
{
  var cached_name = sheet_object.getRange("B2").getValue();
  return cached_name;
}
 
function get_google_pic_url(sheet_object)
{
    var cached_name = sheet_object.getRange("B3").getValue();
    return cached_name;
}
 
function get_previous_pic_url(sheet_object)
{
    var cached_name = sheet_object.getRange("B4").getValue();
    return cached_name;
}
 
function write_cache_data(sheet_object, cache_data, pos)
{
  sheet_object.getRange(pos).setValue(cache_data);
}
 
function http_get_content(request_id)
{
  var response;
  var url = "https://api.line.me/v2/bot/message/" + request_id + "/content";
  response = UrlFetchApp.fetch(url,
  {
  'headers': {
    'Content-Type': 'application/json; charset=UTF-8',
    'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_get_user_name(id)
{
  var response;
  var url = 'https://api.line.me/v2/bot/profile/' + id;
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_get_group_user_name(id, group_id)
{
  var response;
  var url = "https://api.line.me/v2/bot/group/" + group_id + "/member/" + id + "/";
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_get_group_name(group_id)
{
  var response;
  var url = "https://api.line.me/v2/bot/group/" + group_id +"/summary/";
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_send_text_reply(reply_token, send_msg)
{
  var response;
  var url = 'https://api.line.me/v2/bot/message/reply/';
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': reply_token,
      'messages': [{
        'type': 'text',
        'text': send_msg ,
      }],
      'notificationDisabled': 'true',
    }),
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_send_text(id, send_msg)
{
  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_msg,
          "type": "text",
      }],
      'notificationDisabled': 'false',
    }),
  muteHttpExceptions: true,
  });
  return response;
}
 
function http_send_sticker(id, package_id, sticker_id)
{
  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,
  });
  return response;
}
 
function http_send_image(id, img_url, preview_url)
{
  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": "image",
          "originalContentUrl": img_url,
          "previewImageUrl": preview_url,
      }],
      'notificationDisabled': 'false',
    }),
  muteHttpExceptions: true,
  });
  return response;
}
 
function send_msg(id, send_message, time)
{
  var cache;
  var response;
  var return_message = "Success";
 
  response = http_send_text(id, send_message);
  if(response.getResponseCode() != 200)
  {
    cache = "***The message failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message + "*** ";
    cache += send_message;
    send_message = cache;
    return_message = "The message failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
  }
  send_message += "(" + time + ")";
  log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save");
  return return_message;
}
 
function send_sticker(id, package_id, sticker_id, time)
{
  var response;
  var return_message = "Success";
  var send_message = "";
 
  response = http_send_sticker(id, package_id, sticker_id);
  if(response.getResponseCode() != 200)
  {
  send_message = "***The sticker failed to send. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
  return_message = "The sticker failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
  }
  send_message += "<sticker>" + sticker_id + "</sticker>(" + time + ")";
  log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save");
  return return_message;
}
 
function get_cache_data(id, cache_name)
{
  var cache_data = "";
  var cache_file;
  var cache_folder;
  var exist_cache_folders;
  var exist_folders;
  var exist_sub_folders;
  var folder;
  var folder_name = "Line_contents";
  var sub_folder;
 
  exist_folders = DriveApp.searchFolders("'me' in owners");
  folder = check_folder_exist(exist_folders, folder_name);
  if(folder == undefined)
    return undefined;
 
  exist_sub_folders = folder.searchFolders("'me' in owners");
  sub_folder = check_folder_exist(exist_sub_folders, id);
  if(sub_folder == undefined)
    return undefined;
 
  exist_cache_folders = sub_folder.searchFolders("'me' in owners");
  cache_folder = check_folder_exist(exist_cache_folders, cache_name);
  if(cache_folder == undefined)
    return undefined;
 
  for(var i = 0; i < 4096; i++)
  {
    try
    {
      cache_file = cache_folder.getFilesByName(i).next();
      cache_data += cache_file.getBlob().getDataAsString();
    }
    catch(error)
    {
      break;
    }
  }
  cache_folder.setTrashed(true);
  return cache_data;
}
 
function upload_content(id, data, count, cache_name, last, time)
{
  var cache_data;
  var cache_folder;
  var exist_cache_folders;
  var exist_folders;
  var exist_sub_folders;
  var folder;
  var folder_name = "Line_contents";
  var part_of_content;
  var part_of_content_data;
  var return_message = "Success";
  var send_message;
  var sub_folder;
 
  if(count == 0 && last == "true")
    return_message = send_content(id, cache_name, time, data);
  else if(last == "true")
  {
      cache_data = get_cache_data(id, cache_name);
      if(cache_data == undefined)
      {
        send_message = "***The content failed to send. The cache folder does not exist.*** (" + time + ")";
        log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save");
        return_message = "The content failed to send.\nThe cache folder does not exist.";
      }
      else
      {
        cache_data += data;
        return_message = send_content(id, cache_name, time, cache_data);
      }
  }
  else
  {
    exist_folders = DriveApp.searchFolders("'me' in owners");
    folder = check_folder_exist(exist_folders, folder_name);
    if(folder == undefined)
      folder = DriveApp.createFolder(folder_name);
 
    exist_sub_folders = folder.searchFolders("'me' in owners");
    sub_folder = check_folder_exist(exist_sub_folders, id);
    if(sub_folder == undefined)
      sub_folder = folder.createFolder(id);
 
    exist_cache_folders = sub_folder.searchFolders("'me' in owners");
    cache_folder = check_folder_exist(exist_cache_folders, cache_name);
    if(cache_folder == undefined)
      cache_folder = sub_folder.createFolder(cache_name);
    else if(!(cache_folder == undefined) && count == 0)
    {
      cache_folder.setTrashed(true);
      cache_folder = sub_folder.createFolder(cache_name);
    }
 
    part_of_content_data = Utilities.newBlob(data).setName(count);
    part_of_content = cache_folder.createFile(part_of_content_data);
  }
  return return_message;
}
 
function send_content(id, cache_name, time, encoded_data)
{
  var content;
  var content_data;
  var content_type;
  var content_url = "";
  var exist_folders;
  var exist_sub_folders;
  var folder;
  var folder_name = "Line_contents";
  var preview_content_url = "";
  var response;
  var return_message = "Success";
  var send_message = "";
  var sub_folder;
 
  exist_folders = DriveApp.searchFolders("'me' in owners");
  folder = check_folder_exist(exist_folders, folder_name);
  if(folder == undefined)
    folder = DriveApp.createFolder(folder_name);
 
  exist_sub_folders = folder.searchFolders("'me' in owners");
  sub_folder = check_folder_exist(exist_sub_folders, id);
  if(sub_folder == undefined)
    sub_folder = folder.createFolder(id);
 
  try
  {
    content_data = Utilities.newBlob(Utilities.base64Decode(encoded_data)).setName(cache_name);
  }
  catch (e)
  {
    return_message = "The content failed to send.\nCouldn't decode base64 data.";
    return return_message;
  }
 
  content = sub_folder.createFile(content_data);
  content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
  content_type = content.getMimeType();
  if(content_type == MimeType.BMP || content_type == MimeType.GIF || content_type == MimeType.JPEG || content_type == MimeType.PNG || content_type == MimeType.SVG)
  {
    content.setTrashed(true);
    folder_name = "Line_images";
    exist_folders = DriveApp.searchFolders("'me' in owners");
    folder = check_folder_exist(exist_folders, folder_name);
    if(folder == undefined)
    folder = DriveApp.createFolder(folder_name);
 
    exist_sub_folders = folder.searchFolders("'me' in owners");
    sub_folder = check_folder_exist(exist_sub_folders, id);
    if(sub_folder == undefined)
    sub_folder = folder.createFolder(id);
 
    content = sub_folder.createFile(content_data);
    content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
    content_url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w16383-h16383";
    preview_content_url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w240-h240";
 
    response = http_send_image(id, content_url, preview_content_url);
    if(response.getResponseCode() != 200)
    {
      send_message = "***The image failed to send. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
      return_message = "The image failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
    }
    send_message += "<image_url>" + content_url + "</image_url>(" + time + ")";
    log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save");
    return return_message;
  }
  else
  {
    content_url = "https://drive.google.com/file/d/" + content.getId() + "/view";
    response = http_send_text(id, content_url);
    if(response.getResponseCode() != 200)
    {
      send_message = "***The content failed to send. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
      return_message = "The content failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
    }
    content_url = "https://drive.google.com/uc?export=download&id=" + content.getId();
    send_message += "<content_url>" + content_url + "</content_url>(" + time + ")";
    log_save(send_message, CLIENT_NAME, id, "Do not save", "Do not save");
    return return_message;
  }
}
 
function get_profile_pic_google_url(http_response, group_or_user_id)
{
    var content_data;
    var content;
    var folder_name = "Line_images";
    var folder;
    var sub_folder;
    var exist_folders;
    var exist_sub_folders;
    var url = "";
 
    exist_folders = DriveApp.searchFolders("'me' in owners");
    folder = check_folder_exist(exist_folders, folder_name);
    if (folder == undefined)
        folder = DriveApp.createFolder(folder_name);
 
    exist_sub_folders = folder.searchFolders("'me' in owners");
    sub_folder = check_folder_exist(exist_sub_folders, group_or_user_id);
    if (sub_folder == undefined)
        sub_folder = folder.createFolder(group_or_user_id);
 
    content_data = http_response.getBlob().getAs("image/jpeg").setName("icon");
    content = sub_folder.createFile(content_data);
    content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
    url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w32-h32";
 
    return url;
}
 
function get_content_url(request_id, group_or_user_id, type)
{
  var content_data;
  var content;
  var exist_folders;
  var exist_sub_folders;
  var folder_name;
  var folder;
  var sub_folder;
  var response;
  var url = "";
 
  if(type == "image")
    folder_name = "Line_images";
  else if(type == "audio")
    folder_name = "Line_audio";
  else if(type == "video")
    folder_name = "Line_videos";
  else if(type == "file")
    folder_name = "Line_contens";
 
 
  exist_folders = DriveApp.searchFolders("'me' in owners");
  folder = check_folder_exist(exist_folders, folder_name);
  if(folder  == undefined)
    folder = DriveApp.createFolder(folder_name);
 
  exist_sub_folders = folder.searchFolders("'me' in owners");
  sub_folder = check_folder_exist(exist_sub_folders, group_or_user_id);
  if(sub_folder  == undefined)
    sub_folder = folder.createFolder(group_or_user_id);
 
  response = http_get_content(request_id);
  if(response.getResponseCode() != 200)
  {
    if(type == "image")
      url = "***Couldn't generate an image URL. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
    else if(type == "audio")
      url = "***Couldn't generate a audio URL. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
    else if(type == "video")
      url = "***Couldn't generate a video URL. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
  }
  else
  {
    if(type == "image")
    {
      content_data = response.getBlob().getAs("image/jpeg").setName(request_id);
      content = sub_folder.createFile(content_data);
      content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
      url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w16383-h16383";
    }
    else if(type == "audio" || type == "video" || type == "file")
    {
      content_data = response.getBlob().setName(request_id);
      content = sub_folder.createFile(content_data);
      content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
      url = "https://drive.google.com/uc?export=download&id=" + content.getId();
    }
  }
  return url;
}
 
function receive_msg_from_line(user_message, user_id, group_id, reply_token, time, reply)
{
  var group_name;
  var picture_url = [ "", ""];
  var response = [ "", ""];
  var send_msg;
  var user_name;
 
  if(group_id == "Unknown")
    response[0] = http_get_user_name(user_id);
  else
  {
    response[0] = http_get_group_user_name(user_id, group_id);
    response[1] = http_get_group_name(group_id);
 
    if(response[1].getResponseCode() != 200)
      group_name = "Unknown";
    else
    {
      group_name = JSON.parse(response[1]).groupName;
      picture_url[1] = JSON.parse(response[1]).pictureUrl;
    }
  }
 
  if(response[0].getResponseCode() != 200)
    user_name = "Unknown";
  else
  {
    user_name = JSON.parse(response[0]).displayName;
    picture_url[0] = JSON.parse(response[0]).pictureUrl;
  }
 
  if(user_message == "getid" || user_message == "getgroupid")
  {
    if(user_message == "getid")
      send_msg = user_id;
    else if(user_message == "getgroupid")
      send_msg = group_id;
 
    response[0] = http_send_text_reply(reply_token, send_msg);
    send_msg = "<id>" + send_msg + "</id>";
    if(response[0].getResponseCode() != 200)
      send_msg += "***The message failed to send. Status code = " + response[0].getResponseCode() + " " + JSON.parse(response[0].getContentText()).message + "*** ";
 
    send_msg += "(" + time + ")";
    if(user_message == "getid")
      log_save(send_msg, user_name, "IDs", "BOT", "Do not save");
    else if(user_message == "getgroupid")
      log_save(send_msg, group_name, "IDs", "BOT", "Do not save");
    return;
  }
  user_message += "(" + time + ")";
  if(group_id == "Unknown")
    log_save(user_message, user_name, user_id, user_name, picture_url[0]);
  else
    log_save(user_message, user_name, group_id, group_name, picture_url[1]);
}
 
function doPost(post_data)
{
  var time = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'MM/dd hh:mm:ss');
  var data_type;
  var result;
  var client_auth = "Unknown";
  var client_gas_ver = "Unknown";
  var id = "Unknown";
  var lock = LockService.getScriptLock();
  var lock_result = lock.tryLock(100);
  try
  {
    data_type = JSON.parse(post_data.postData.contents).type;
  }
  catch(e)
  {
  }
  while(!lock_result)
  {
    Utilities.sleep(100);
    lock_result = lock.tryLock(100);
  }
 
  if(data_type == undefined)
  {
    for(var i = 0; i < 50; i++)
    {
      var msg_id = "";
      var user_message = "";
      var user_id = "";
      var group_id = "";
      var reply_token = "";
      var msg_type = "";
      var type = "";
      try
      {
        type = JSON.parse(post_data.postData.contents).events[i].type;
        user_id = JSON.parse(post_data.postData.contents).events[i].source.userId;
        group_id = JSON.parse(post_data.postData.contents).events[i].source.groupId;
        reply_token = JSON.parse(post_data.postData.contents).events[i].replyToken;
        if(type == "message")
        {
          msg_id = JSON.parse(post_data.postData.contents).events[i].message.id;
          user_message = JSON.parse(post_data.postData.contents).events[i].message.text;
          msg_type = JSON.parse(post_data.postData.contents).events[i].message.type;
        }
      }
      catch(e)
      {
        break;
      }
 
      if(user_id == undefined)
        user_id = "Unknown";
      if(group_id == undefined)
        group_id = "Unknown";
 
 
      if(type == "follow")
        user_message = "getid";
      else if(type == "join")
        user_message = "getgroupid";
      if(type == "leave")
        user_message = "*****Removed by user*****";
      else if(type == "unfollow")
        user_message = "*****Blocked by user*****";
      else if(msg_type == "sticker")
      {
        var sticker_id  = JSON.parse(post_data.postData.contents).events[i].message.stickerId;
        user_message = "<sticker>" + sticker_id + "</sticker>";
      }
      else if(msg_type == "image")
      {
      if(group_id == "Unknown")
        user_message = "<image_url>" + get_content_url(msg_id, user_id, msg_type) + "</image_url>";
      else
        user_message = "<image_url>" + get_content_url(msg_id, group_id, msg_type) + "</image_url>";
      }
      else if(msg_type == "audio")
      {
      if(group_id == "Unknown")
        user_message = "<audio_url>" + get_content_url(msg_id, user_id, msg_type) + "</audio_url>";
      else
        user_message = "<audio_url>" + get_content_url(msg_id, group_id, msg_type) + "</audio_url>";
      }
      else if(msg_type == "video")
      {
      if(group_id == "Unknown")
        user_message = "<video_url>" + get_content_url(msg_id, user_id, msg_type) + "</video_url>";
      else
        user_message = "<video_url>" + get_content_url(msg_id, group_id, msg_type) + "</video_url>";
      }
      else if(msg_type == "file")
      {
      if(group_id == "Unknown")
        user_message = "<file_url>" + get_content_url(msg_id, user_id, msg_type) + "</file_url>";
      else
        user_message = "<file_url>" + get_content_url(msg_id, group_id, msg_type) + "</file_url>";
      }
      else if(user_message == undefined)
        user_message += " : " + msg_type + " " + msg_id;
 
      receive_msg_from_line(user_message, user_id, group_id, reply_token, time);
    }
    lock.releaseLock();
    return;
  }
 
  try
  {
    client_auth = JSON.parse(post_data.postData.contents).auth;
    client_gas_ver = JSON.parse(post_data.postData.contents).gas_ver;
    id = JSON.parse(post_data.postData.contents).id;
  }
  catch(e)
  {
  }
 
  if(SCRIPT_PASSOWRD == client_auth)
  {
    if(gas_ver == client_gas_ver)
    {
      if(data_type == "send_text")
      {
        var send_message = JSON.parse(post_data.postData.contents).message;
        result = send_msg(id, send_message, time);
      }
      else if(data_type == "send_sticker")
      {
        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 if(data_type == "upload_content")
      {
        var content_data = JSON.parse(post_data.postData.contents).content_data;
        var count = JSON.parse(post_data.postData.contents).count;
        var name = JSON.parse(post_data.postData.contents).name;
        var last = JSON.parse(post_data.postData.contents).last;
        result = upload_content(id, content_data, count, name, last, 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.";
 
  lock.releaseLock();
  return ContentService.createTextOutput(result);
}
 
function doGet(post_data)
{
  var data = "";
  var client_auth = "";
  var client_gas_ver = "";
  var num_of_logs = "";
  var id = "";
  var lock = LockService.getScriptLock();
  var lock_result = lock.tryLock(100);
  while(!lock_result)
  {
    Utilities.sleep(100);
    lock_result = lock.tryLock(100);
  }
  try
  {
    client_auth = post_data.parameter.script_auth;
    client_gas_ver = post_data.parameter.gas_ver;
    num_of_logs = post_data.parameter.logs;
    id = post_data.parameter.id;
  }
  catch(e)
  {
  }
  if(client_auth == undefined)
    client_auth = "";
  if(client_gas_ver == undefined)
    client_gas_ver = -1;
  if(num_of_logs == undefined)
    num_of_logs = 300;
  if(id == undefined)
    id = "unknown";
 
  if(SCRIPT_PASSOWRD == client_auth)
  {
    if(gas_ver == client_gas_ver)
      data = log_read(id, num_of_logs);
    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.";
 
  lock.releaseLock();
  return ContentService.createTextOutput(data);
}
</syntaxhighlight>
  </div>
  <div class="content" id="panel3">
    <p>This code compatible with ver 1.5.0~1.5.2 !!!WARNING!!! I strongly recommend you use the newest version !!!WARNING!!!</p>
<syntaxhighlight lang="C++" style="border: 3px dashed blue;">
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 = 5;//Do **NOT** edit this value.
function log_save(message, user_name, write_sheet_name, group_or_user_name, pic_url)
{
    var google_pic_url;
    var previous_pic_url;
    var response;
    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_data(write_sheet, (sheet_pos + 1), "B1");
    if (group_or_user_name != "Do not save")
        write_cache_data(write_sheet, group_or_user_name, "B2");
 
    if (pic_url != "Do not save")
    {
        previous_pic_url = get_previous_pic_url(write_sheet);
        if (previous_pic_url != pic_url)
        {
            response = UrlFetchApp.fetch(pic_url,
                {
                    'headers': {
                        'Content-Type': 'application/json; charset=UTF-8',
                        'Authorization': 'Bearer ' + ACCESS_TOKEN,
                    },
                    muteHttpExceptions: true,
                });
 
            if (response.getResponseCode() == 200)
            {
                google_pic_url = get_profile_pic_google_url(response, write_sheet_name);
                write_cache_data(write_sheet, google_pic_url, "B3");
                write_cache_data(write_sheet, pic_url, "B4");
            }
        }
 
    }
}
 
function check_folder_exist(input_folders, name)
{
    var folder;
    while (input_folders.hasNext())
    {
      folder = input_folders.next();
      if(name == folder.getName())
        return folder;
    }
    return undefined;
}
 
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_data(read_sheet, sheet_start, "B1");
      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();
  return_data = "<0>";
  for(var i = 0; i < 300; i++)
  {
    if(sheet_data[i] == "")
      break;
 
    return_data += sheet_data[i];
  }
    return_data += "</0>";
    return_data += "<1>" + get_cache_name(read_sheet) + "</1>";
    return_data += "<2>" + get_google_pic_url(read_sheet) + "</2>";
    return_data += "<3>Success</3>";
  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 get_cache_name(sheet_object)
{
  var cached_name = sheet_object.getRange("B2").getValue();
  return cached_name;
}
 
function get_google_pic_url(sheet_object)
{
    var cached_name = sheet_object.getRange("B3").getValue();
    return cached_name;
}
 
function get_previous_pic_url(sheet_object)
{
    var cached_name = sheet_object.getRange("B4").getValue();
    return cached_name;
}
 
function write_cache_data(sheet_object, cache_data, pos)
{
  sheet_object.getRange(pos).setValue(cache_data);
}
 
function http_get_content(request_id)
{
  var response;
  var url = "https://api.line.me/v2/bot/message/" + request_id + "/content";
  response = UrlFetchApp.fetch(url,
  {
  'headers': {
    'Content-Type': 'application/json; charset=UTF-8',
    'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_get_user_name(id)
{
  var response;
  var url = 'https://api.line.me/v2/bot/profile/' + id;
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_get_group_user_name(id, group_id)
{
  var response;
  var url = "https://api.line.me/v2/bot/group/" + group_id + "/member/" + id + "/";
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_get_group_name(group_id)
{
  var response;
  var url = "https://api.line.me/v2/bot/group/" + group_id +"/summary/";
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_send_text_reply(reply_token, send_msg)
{
  var response;
  var url = 'https://api.line.me/v2/bot/message/reply/';
  response = UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': reply_token,
      'messages': [{
        'type': 'text',
        'text': send_msg ,
      }],
      'notificationDisabled': 'true',
    }),
    muteHttpExceptions: true,
  });
  return response;
}
 
function http_send_text(id, send_msg)
{
  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_msg,
          "type": "text",
      }],
      'notificationDisabled': 'false',
    }),
  muteHttpExceptions: true,
  });
  return response;
}
 
function http_send_sticker(id, package_id, sticker_id)
{
  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,
  });
  return response;
}
 
function http_send_image(id, img_url, preview_url)
{
  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": "image",
          "originalContentUrl": img_url,
          "previewImageUrl": preview_url,
      }],
      'notificationDisabled': 'false',
    }),
  muteHttpExceptions: true,
  });
  return response;
}
 
function send_msg(id, send_message, time)
{
  var cache;
  var response;
  var return_message = "Success";
  response = http_send_text(id, send_message);
  if(response.getResponseCode() != 200)
  {
    cache = "***The message failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message + "*** ";
    cache += send_message;
    send_message = cache;
    return_message = "The message failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
  }
  send_message += "(" + time + ")";
  log_save(send_message, account_name_of_3ds, id, "Do not save", "Do not save");
  return return_message;
}
 
function send_sticker(id, package_id, sticker_id, time)
{
  var response;
  var return_message = "Success";
  var send_message = "";
 
  response = http_send_sticker(id, package_id, sticker_id);
  if(response.getResponseCode() != 200)
  {
  send_message = "***The sticker failed to send. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
  return_message = "The sticker failed to send. 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, "Do not save", "Do not save");
  return return_message;
}
 
function upload_content(id, data, count, cache_name)
{
  var cache_folder;
  var exist_cache_folders;
  var exist_folders;
  var exist_sub_folders;
  var folder;
  var folder_name = "Line_contents";
  var part_of_content;
  var part_of_content_data;
  var return_message = "Success";
  var sub_folder;
 
  exist_folders = DriveApp.searchFolders("'me' in owners");
  folder = check_folder_exist(exist_folders, folder_name);
  if(folder == undefined)
    folder = DriveApp.createFolder(folder_name);
  exist_sub_folders = folder.searchFolders("'me' in owners");
  sub_folder = check_folder_exist(exist_sub_folders, id);
  if(sub_folder == undefined)
    sub_folder = folder.createFolder(id);
  exist_cache_folders = sub_folder.searchFolders("'me' in owners");
  cache_folder = check_folder_exist(exist_cache_folders, cache_name);
  if(cache_folder == undefined)
    cache_folder = sub_folder.createFolder(cache_name);
  else if(!(cache_folder == undefined) && count == 0)
  {
    cache_folder.setTrashed(true);
    cache_folder = sub_folder.createFolder(cache_name);
  }
 
  part_of_content_data = Utilities.newBlob(data).setName(count);
  part_of_content = cache_folder.createFile(part_of_content_data);
  return return_message;
}
 
function send_content(id, cache_name, time)
{
  var cache_file;
  var cache_folder;
  var content;
  var content_data;
  var content_type;
  var content_url = "";
  var encoded_data = "";
  var exist_cache_folders;
  var exist_folders;
  var exist_sub_folders;
  var folder;
  var folder_name = "Line_contents";
  var preview_content_url = "";
  var response;
  var return_message = "Success";
  var send_message = "";
  var sub_folder;
 
  exist_folders = DriveApp.searchFolders("'me' in owners");
  folder = check_folder_exist(exist_folders, folder_name);
  if(folder == undefined)
    folder = DriveApp.createFolder(folder_name);
 
  exist_sub_folders = folder.searchFolders("'me' in owners");
  sub_folder = check_folder_exist(exist_sub_folders, id);
  if(sub_folder == undefined)
    sub_folder = folder.createFolder(id);
 
  exist_cache_folders = sub_folder.searchFolders("'me' in owners");
  cache_folder = check_folder_exist(exist_cache_folders, cache_name);
  if(cache_folder == undefined)
  {
    send_message = "***The content failed to send. The cache folder does not exist.*** (" + time + ")";
    log_save(send_message, account_name_of_3ds, id, "Do not save", "Do not save");
    return_message = "The content failed to send.\nThe cache folder does not exist."
    return return_message;
  }
 
  for(var i = 0; i < 4096; i++)
  {
    try
    {
      cache_file = cache_folder.getFilesByName(i).next();
      encoded_data += cache_file.getBlob().getDataAsString();
    }
    catch(error)
    {
      break;
    }
  }
 
  cache_folder.setTrashed(true);
  content_data = Utilities.newBlob(Utilities.base64Decode(encoded_data)).setName(cache_name);
  content = sub_folder.createFile(content_data);
  content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
  content_type = content.getMimeType();
  if(content_type == MimeType.BMP || content_type == MimeType.GIF || content_type == MimeType.JPEG || content_type == MimeType.PNG || content_type == MimeType.SVG)
  {
    content.setTrashed(true);
    folder_name = "Line_images";
    exist_folders = DriveApp.searchFolders("'me' in owners");
    folder = check_folder_exist(exist_folders, folder_name);
    if(folder == undefined)
    folder = DriveApp.createFolder(folder_name);
 
    exist_sub_folders = folder.searchFolders("'me' in owners");
    sub_folder = check_folder_exist(exist_sub_folders, id);
    if(sub_folder == undefined)
    sub_folder = folder.createFolder(id);
    content = sub_folder.createFile(content_data);
    content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
    content_url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w16383-h16383";
    preview_content_url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w240-h240";
    response = http_send_image(id, content_url, preview_content_url);
    if(response.getResponseCode() != 200)
    {
      send_message = "***The image failed to send. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
      return_message = "The image failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
    }
    send_message += "<image_url>" + content_url + "</image_url>(" + time + ")";
    log_save(send_message, account_name_of_3ds, id, "Do not save", "Do not save");
    return return_message;
  }
  else
  {
    content_url = "https://drive.google.com/file/d/" + content.getId() + "/view";
    response = http_send_text(id, content_url);
    if(response.getResponseCode() != 200)
    {
      send_message = "***The content failed to send. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
      return_message = "The content failed to send. Status code = " + response.getResponseCode() + "\n" + JSON.parse(response.getContentText()).message;
    }
    content_url = "https://drive.google.com/uc?export=download&id=" + content.getId();
    send_message += "<content_url>" + content_url + "</content_url>(" + time + ")";
    log_save(send_message, account_name_of_3ds, id, "Do not save", "Do not save");
    return return_message;
  }
}
 
function get_profile_pic_google_url(http_response, group_or_user_id)
{
    var content_data;
    var content;
    var folder_name = "Line_images";
    var folder;
    var sub_folder;
    var exist_folders;
    var exist_sub_folders;
    var url = "";
    exist_folders = DriveApp.searchFolders("'me' in owners");
    folder = check_folder_exist(exist_folders, folder_name);
    if (folder == undefined)
        folder = DriveApp.createFolder(folder_name);
 
    exist_sub_folders = folder.searchFolders("'me' in owners");
    sub_folder = check_folder_exist(exist_sub_folders, group_or_user_id);
    if (sub_folder == undefined)
        sub_folder = folder.createFolder(group_or_user_id);
 
    content_data = http_response.getBlob().getAs("image/jpeg").setName("icon");
    content = sub_folder.createFile(content_data);
    content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
    url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w32-h32";
 
    return url;
}
 
function get_content_url(request_id, group_or_user_id, type)
{
  var content_data;
  var content;
  var exist_folders;
  var exist_sub_folders;
  var folder_name;
  var folder;
  var sub_folder;
  var response;
  var url = "";
  if(type == "image")
    folder_name = "Line_images";
  else if(type == "audio")
    folder_name = "Line_audio";
  else if(type == "video")
    folder_name = "Line_videos";
  exist_folders = DriveApp.searchFolders("'me' in owners");
  folder = check_folder_exist(exist_folders, folder_name);
  if(folder  == undefined)
    folder = DriveApp.createFolder(folder_name);
  exist_sub_folders = folder.searchFolders("'me' in owners");
  sub_folder = check_folder_exist(exist_sub_folders, group_or_user_id);
  if(sub_folder  == undefined)
    sub_folder = folder.createFolder(group_or_user_id);
 
  response = http_get_content(request_id);
  if(response.getResponseCode() != 200)
  {
    if(type == "image")
      url = "***Couldn't generate an image URL. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
    else if(type == "audio")
      url = "***Couldn't generate a audio URL. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
    else if(type == "video")
      url = "***Couldn't generate a video URL. Status code = " + response.getResponseCode() + " " + JSON.parse(response.getContentText()).message + "*** ";
  }
  else
  {
    if(type == "image")
    {
      content_data = response.getBlob().getAs("image/jpeg").setName(request_id);
      content = sub_folder.createFile(content_data);
      content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
      url = "https://lh3.googleusercontent.com/d/" + content.getId() + "=w16383-h16383";
    }
    else if(type == "audio" || type == "video")
    {
      content_data = response.getBlob().setName(request_id);
      content = sub_folder.createFile(content_data);
      content.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
      url = "https://drive.google.com/uc?export=download&id=" + content.getId();
    }
  }
  return url;
}
 
function receive_msg_from_line(user_message, user_id, group_id, reply_token, time)
{
  var group_name;
  var picture_url = [ "", ""];
  var response = [ "", ""];
  var send_msg;
  var user_name;
  if(user_message == "getid" || user_message == "getgroupid")
  {
    if(user_message == "getid")
      send_msg = user_id;
    else if(user_message == "getgroupid")
      send_msg = group_id;
    response[0] = http_send_text_reply(reply_token, send_msg);
    send_msg = "<id>" + send_msg + "</id>";
    if(response[0].getResponseCode() != 200)
      send_msg += "***The message failed to send. Status code = " + response[0].getResponseCode() + " " + JSON.parse(response[0].getContentText()).message + "*** ";
 
    log_save(send_msg, "BOT", "IDs", "BOT", "Do not save");
    return;
  }
 
  if(group_id == "Unknown")
    response[0] = http_get_user_name(user_id);
  else
  {
    response[0] = http_get_group_user_name(user_id, group_id);
    response[1] = http_get_group_name(group_id);
    if(response[1].getResponseCode() != 200)
      group_name = "Unknown";
    else
    {
      group_name = JSON.parse(response[1]).groupName;
      picture_url[1] = JSON.parse(response[1]).pictureUrl;
    }
  }
 
  if(response[0].getResponseCode() != 200)
    user_name = "Unknown";
  else
  {
    user_name = JSON.parse(response[0]).displayName;
    picture_url[0] = JSON.parse(response[0]).pictureUrl;
  }
  user_message += "(" + time + ")";
  if(group_id == "Unknown")
    log_save(user_message, user_name, user_id, user_name, picture_url[0]);
  else
    log_save(user_message, user_name, group_id, group_name, picture_url[1]);
}
 
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;
  var lock = LockService.getScriptLock();
  var lock_result = lock.tryLock(1000);
  while(!lock_result)
  {
    Utilities.sleep(100);
    lock_result = lock.tryLock(1000);
  }
 
  if(type == undefined)
  {
    for(var i = 0; i < 30; i++)
    {
      try
      {
        var msg_id = JSON.parse(post_data.postData.contents).events[i].message.id;
        var user_message = JSON.parse(post_data.postData.contents).events[i].message.text;
        var user_id = JSON.parse(post_data.postData.contents).events[i].source.userId;
        var group_id = JSON.parse(post_data.postData.contents).events[i].source.groupId;
        var reply_token = JSON.parse(post_data.postData.contents).events[i].replyToken;
        var type = JSON.parse(post_data.postData.contents).events[i].message.type;
      }
      catch(error)
      {
        break;
      }
      if(user_id == undefined)
        user_id = "Unknown";
      if(group_id == undefined)
        group_id = "Unknown";
 
      if(type == "sticker")
      {
        var sticker_id  = JSON.parse(post_data.postData.contents).events[0].message.stickerId;
        user_message = "<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, reply_token, time);
    }
    lock.releaseLock();
    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)
    {
      var id = JSON.parse(post_data.postData.contents).id;
      if(type == "send_text")
      {
        var send_message = JSON.parse(post_data.postData.contents).message;
        result = send_msg(id, send_message, time);
      }
      else if(type == "send_sticker")
      {
        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 if(type == "upload_content")
      {
        var content_data = JSON.parse(post_data.postData.contents).content_data;
        var count = JSON.parse(post_data.postData.contents).count;
        var name = JSON.parse(post_data.postData.contents).name;
        result = upload_content(id, content_data, count, name);
      }
      else if(type == "send_content")
      {
        var name = JSON.parse(post_data.postData.contents).name;
        result = send_content(id, name, 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.";
 
  lock.releaseLock();
  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);
    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);
}
</syntaxhighlight>
  </div>
</div>
 
''''''Then click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Publish </span></span>'''-&gt; '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Deploy as web app</span></span>''' and set<br />
<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">&quot;'''Project version'''&quot;</span></span> : '''<span style="font-size: 15px"><span style="color: #00b300">New</span><br />
'''<span style="color: rgb(77, 77, 255)"><span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">&quot;Execute the app as&quot;</span></span> : </span>''''''<span style="color: #00b300"><span style="font-size: 15px">Me(***@gmail.com)</span></span>'''''''''</span>'''''''''<br />
<span style="font-size: 15px"><span style="font-size: 15px"><span style="font-size: 15px"><span style="color: rgb(77, 77, 255)">'''<span style="color: rgb(0, 0, 255)">&quot;'''Who has access to the app'''&quot;</span>'''</span></span><span style="color: #4d4dff"> ''':''' </span><span style="font-size: 15px"><span style="color: #00b300">'''Anyone,even anonymous'''</span></span></span></span><br />
''''''finally click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">deploy</span></span>.<br />
'''''''''<br />
https://dlhb.gamebrew.org/3dshomebrew/2380c28d8c9f5f0fdba5bcaad06b5e09.png
<br />
'''Next click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Review permissions </span></span>'''and select your google account.<br />
If &quot;'''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">This app isn't verified </span></span><span style="font-size: 12px">or </span><span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">You should avoid this app</span></span>'''&quot; shown click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Advanced </span></span>'''and click <span style="font-size: 15px">&quot;</span>'''<span style="color: rgb(0, 0, 255)">'''<span style="font-size: 15px">Go to Line main (unsafe)&quot;</span>''' </span>'''<span style="font-size: 12px"><span style="color: #b30000"><span style="text-decoration: underline">(⁂It's safe!<span style="text-decoration: underline">'''⁂'''</span>)</span></span> </span>and click '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">allow.</span></span><br />
Copy <span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">'''this url''' </span></span>and<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)"> '''save.'''<br />
</span></span>''''''<br />
https://dlhb.gamebrew.org/3dshomebrew/578f8adc76932a8223d45d904303d707.png<br />
'''<br />
''''''Back to <span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Line Messaging API</span></span><br />
Find '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Webhook URL </span></span>'''and click '''<span style="color: rgb(0, 0, 255)"><span style="font-size: 15px">edit.<br />
</span></span><br />
'''<span style="font-size: 26px"><span style="color: rgb(255, 0, 0)">⁂ DO NOT TYPE SHORT URL HERE ⁂</span></span>''''''''''''<br />
Type your''' Google apps script URL '''and click <span style="color: rgb(0, 0, 255)"><span style="font-size: 15px">'''Update'''<br />
</span></span>'''<br />
https://dlhb.gamebrew.org/3dshomebrew/003b8ec8407b4db43fef03a9a6337a6b.png<br />
'''And turn on''' ''''''<span style="color: rgb(0, 0, 255)"><span style="font-size: 15px">'''Use webhook'''</span></span>''''''<br />
https://dlhb.gamebrew.org/3dshomebrew/5dc4172fb531eacaf26757aa3fdb1b64.png<br />
<br />
'''Click''' <span style="color: #0000ff">'''verify'''</span> '''to check webhook works fine.<br />
Check result and if<span style="color: #000000"> &quot;</span><span style="color: #0000ff">success</span><span style="color: #000000">&quot; </span>wasn't displayed, double check your Google apps script URL.'''<span style="color: #000000">.</span><br />
(delete whole url once and retyping is also good idea)<br />
<br />
'''Please open<span style="color: rgb(179, 0, 0)"> https://free-url-shortener.rb.gy/ </span>''', https://tiny.cc/ or https://tinyurl.com/<br />
<span style="font-size: 15px"><span style="color: rgb(0, 0, 0)">'''Then shorten your'''</span></span> Google apps script URL.<br />
Your URL should be<br />
[https://tinyurl.com/***** http://tinyurl.com/******] or [https://tiny.cc/***** http://tiny.cc/*****]* or '''http://rb.gy/******<br />
This short URL is needed by next section.<br />
Then go to <span style="color: #000000">(Guide) How to setup (3DS)</span>.<br />
<br />
If you have any problems fell free to ask me on this discord server.<br />
'''[https://discord.gg/bzGnu2a <span style="color: #000000">https://discord.gg/bzGnu2a</span>]''''''
 
</div>
</div>
 
== How To Setup (3DS)==
<div class="toccolours mw-collapsible mw-collapsed">
Please click here to see the contents:
<div class="mw-collapsible-content">
'''Download the latest Line For 3DS'''
 
Start app and select '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Line</span></span>.'''<br />
Set your password (if you don't want use password, enter as empty).<br />
 
'''For ver 1.5.0~1.7.0'''
 
 
'''<span style="color: rgb(0, 179, 89)"><span style="font-size: 26px">'''This guide compatible with''' </span></span>'''<span style="font-size: 26px"><span style="color: rgb(0, 179, 89)">ver </span><span style="color: rgb(0, 0, 255)">1.5.0</span>'''<span style="color: rgb(0, 179, 89)">~</span><span style="color: #ff0000">1.7.0</span>'''</span>''''''


Next step there are 2 methods<br />
==Changelog==
'''v2.0.0.1 2024/01/30'''
* Line:
** Significant changes for internal code, so you need to setup your Google Apps Script again.
** [https://github.com/Core-2-Extreme/Line_for_3DS/blob/v2.0.0/SETUP.md Setup instructions] are updated.
** 3DS:
*** Chat log wrapping feature has been improved a lot.
*** Auto update feature (for chat logs) are removed temporary.
*** Clear cache feature (for downloaded files) are removed temporary.
** Google Apps Script:
*** Self tests are added so that it is much easier to trouble shoot.
* Camera:
** Supported QR code scan (currently, only for Google Apps Script setup).
* Video player:
** Updated to v1.5.3 (see [https://github.com/Core-2-Extreme/Video_player_for_3DS/blob/v1.5.3#v153 Video player for 3DS patch note] for details).
* Overall:
** Application folder has been changed from <code>/Line/</code> to <code>/3ds/Line/</code> (old folder won't be deleted automatically, but you can delete it if you don't need it).
** Sub application icon has been added.


====Method 1 (Easy)====
'''v2.0.0 2023/01/27'''
Press '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">L + R button</span></span>''' (or tap '''<span style="color: #0000ff">Change main URL (short)</span>''') and type your short url<br />
* Line:
You <span style="font-size: 22px"><span style="color: #ff0000">'''MUST'''</span></span> type short URL with '''<span style="font-size: 18px"><span style="color: #0000ff">http://</span></span>''' NOT <span style="color: #0000ff">'''http'''</span>'''<span style="font-size: 18px"><span style="color: #ff0000">s</span></span><span style="color: #0000ff">:// </span>'''(I don't know why but '''https:// won't work'''.)
** Significant changes for internal code, so you need to setup your Google Apps Script again.
** [https://github.com/Core-2-Extreme/Line_for_3DS/blob/v2.0.0/SETUP.md Setup instructions] are updated.
** 3DS:
*** Chat log wrapping feature has been improved a lot.
*** Auto update feature (for chat logs) are removed temporary.
*** Clear cache feature (for downloaded files) are removed temporary.
** Google Apps Script:
*** Self tests are added so that it is much easier to trouble shoot.
* Camera:
** Supported QR code scan (currently, only for Google Apps Script setup).
* Video player:
** Updated to v1.5.3 (see [https://github.com/Core-2-Extreme/Video_player_for_3DS/blob/v1.5.3#v153 Video player for 3DS patch note] for details).
* Overall:
** Application folder has been changed from <code>/Line/</code> to <code>/3ds/Line/</code> (old folder won't be deleted automatically, but you can delete it if you don't need it).
** Sub application icon has been added.


====Method 2 (Very very very very very very hard)====
'''v1.7.2 2021/01/07'''
* [Line] Fixed some bugs.
* [Vid] Added debug infomation.
* [Vid] Changed initial image size and position.
* [App] Some minor update to better user experience.


I do '''NOT''' recommend this method because it is hell ;(<br />
'''v1.7.1 2020/12/13'''
<span style="color: #ff0000"><span style="font-size: 26px">'''if'''</span> you want</span> to type whole URL, press <span style="color: #0000ff">'''X button''' </span>(or tap <span style="color: #0000ff">'''Change main URL'''</span>) and type your google apps script URL.
* [Line] Added Supported download all files.
* [Mup/Vid] Added Additional formats support(.ogg).
* [App] Some minor update to better user experience.


'''Then go to <span style="color: #000000">(Guide)How to add friend</span>.'''<br />
'''v1.7.0 2020/12/03'''
'''If you have any problems fell free to ask me on this discord server.'''<br />
* [App] Fixed Fonts was improved.
'''<span style="color: rgb(0, 0, 0)">https://discord.gg/bzGnu2a</span>'''<br />
* [App] Added Video player.
</div>
* [Line] Added Now, you can play audio and video sent by user.
</div>
* [Cam] Fixed Framerate was improved.
== How To Add Friend==
* [Mup] Added Additional formats (like aac) are supported.
<div class="toccolours mw-collapsible mw-collapsed">
* [App] Some minor update to better user experience.
Please click here to see the contents:
<div class="mw-collapsible-content">


'''PC''' or '''phone ('''or '''android emulator)<br />
'''v1.6.0 2020/09/12'''
'''Find''' <span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Bot information </span></span>and scan your<span style="font-size: 15px"> <span style="color: rgb(0, 0, 255)">bot's QR code</span></span> or search friends by <span style="color: rgb(0, 0, 255)"><span style="font-size: 18px">BOT basic ID</span>.</span>'''<br />
* [Line] Fixed- The app will crash if use 'send a file' function.
https://dlhb.gamebrew.org/3dshomebrew/c5fce08f621dcbde7f904b526a0dde38-png<br />
* [Line] Fixed- Improved 'send a file' function processing speed.
* [Line] Added- Search function.
* [Line] Added- Now, you can receive up to 4000 chat logs.
* [Music player] Fixed- Improved mp3 play quality.
* [Music player] Added- Seek function.
* [Explorer] Added- Now, file will be sorted.


<ul class="tabs" data-tab>
'''v1.5.2 2020/07/14'''
  <li class="tab-title active"><a href="#panel1">For ver 1.6.0~1.70</a></li>
* [Line] Fixed- Adjust some UI.
  <li class="tab-title"><a href="#panel2">For ver 1.5.2~1.5.0</a></li>
* [Camera] Fixed- Some settings will be reset after restarting the camera.
  <li class="tab-title"><a href="#panel3">For ver 1.4.2~1.2.0</a></li>
* [Mic] Fixed- The app will crash if couldn't allocate memory(out of memory).
</ul>
* [Music player] Fixed- The app sometimes crash when try playing .mp3 files.
<div class="tabs-content">
* [Music player] Added- Shuffle function.
  <div class="content active" id="panel1">
'''<span style="font-size: 15px"><span style="color: rgb(0, 179, 89)"><span style="font-size: 26px">'''This guide compatible with''' </span></span>'''<span style="font-size: 26px"><span style="color: rgb(0, 179, 89)">ver </span><span style="color: #0000ff">1.6.0</span>'''''''''<span style="color: #00b359">~</span><span style="color: #ff0000">1.7.0</span>'''''''''</span>'''</span>'''
<span style="font-size: 15px">'''3DS'''<br />
<br />
Press '''<span style="color: rgb(0, 0, 255)">Y button</span>''' (or tap '''<span style="color: rgb(0, 0, 255)">Add new ID</span>)''' and '''<span style="color: rgb(0, 0, 255)">type &quot;</span><span style="color: rgb(255, 0, 0)">IDs</span><span style="color: rgb(0, 0, 255)">&quot;.</span>'''</span><br />
https://dlhb.gamebrew.org/3dshomebrew/213847-b395a998bbadc9da090a8a8c1643f28e.jpg
https://dlhb.gamebrew.org/3dshomebrew/213848-a22714cf4f2c5ad4f8faee777fbb10df.jpg
<span style="font-size: 15px"><br />
'''<span style="color: rgb(0, 0, 255)">Select &quot;</span><span style="color: rgb(255, 0, 0)">IDs</span><span style="color: rgb(0, 0, 255)">&quot;.</span>'''</span><br />
https://dlhb.gamebrew.org/3dshomebrew/213850-6584a852100d8425ac9eabb795203415.jpg
<span style="font-size: 15px"><br />
Tap '''&quot;<span style="color: rgb(0, 0, 255)">Receive</span>&quot;''' tab and press <span style="color: rgb(0, 0, 255)">'''B button'''</span> (or tap'''<span style="color: rgb(0, 0, 255)"> log update</span>''')</span><br />
https://dlhb.gamebrew.org/3dshomebrew/213851-a7224391e6bd5598948ce90f56e1fa82.jpg
<span style="font-size: 15px"><br />
You'll see '''<span style="color: rgb(0, 0, 255)">touch to add this ID </span>and <span style="color: rgb(0, 0, 255)">tap it.</span>'''<br />
</span><br />
https://dlhb.gamebrew.org/3dshomebrew/00000029.jpg
https://dlhb.gamebrew.org/3dshomebrew/00000031.jpg
https://dlhb.gamebrew.org/3dshomebrew/00000033.jpg
</span>


  </div>
'''v1.5.1 2020/06/30'''
  <div class="content" id="panel2">
* [App] Fixed- The message will not be displayed correctly if the Line folder does not exist.
<span style="font-size: 15px"><br />
* [Line] Fixed- The message will not be sent if double quote or backslash was included.
And send &quot;</span>'''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">getid</span></span>'''<span style="font-size: 15px">&quot;.</span><br />
* [Line] Added- Now, you can include new lines in the message.
https://dlhb.gamebrew.org/3dshomebrew/00000029.png
* [Music player] Fixed- A memory leak occurs when playing .mp3 files.
</span>


'''<span style="font-size: 15px"><span style="color: rgb(0, 179, 89)"><span style="font-size: 26px">'''This guide compatible with''' </span></span>'''<span style="font-size: 26px"><span style="color: rgb(0, 179, 89)">ver </span><span style="color: rgb(0, 0, 255)">1.5.0</span>'''<span style="color: rgb(0, 179, 89)">~</span><span style="color: rgb(255, 0, 0)">1.5.2</span>'''</span>'''</span>'''<br />
'''v1.5.0 2020/06/22'''
<span style="font-size: 15px"><span style="background-color:AQUA"><span style="font-size: 22px"> ''''''<span style="color: rgb(255, 0, 0)">⚠⚠⚠WARNING⚠⚠⚠</span>'''''' </span></span> '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 0)"><span style="font-size: 22px">I strongly recommend you use the newest version</span></span></span>''' <span style="background-color:AQUA"><span style="font-size: 22px"> '''<span style="color: rgb(255, 0, 0)">⚠⚠⚠WARNING⚠⚠⚠</span>''' </span></span></span><br />
* [App] Added- Camera, Mic and Music player.
<span style="font-size: 15px"><br />
* [App] Build with the latest version of ctrlib(v1.9.0)
'''3DS'''<br />
* [Line] Fixed- UI design.
<br />
* [Line] Fixed- Made setup steps easier.
Press <span style="font-size: 15px">'''<span style="color: rgb(0, 0, 255)">Y button</span>''' (or tap '''<span style="color: rgb(0, 0, 255)">Add new ID</span>)''' </span>and<span style="font-size: 15px"> '''<span style="color: rgb(0, 0, 255)">type &quot;</span><span style="color: rgb(255, 0, 0)">IDs</span><span style="color: rgb(0, 0, 255)">&quot;.</span>'''</span><br />
* [Line] Added- Display user/group name and user/group icon.
https://dlhb.gamebrew.org/3dshomebrew/213847-b395a998bbadc9da090a8a8c1643f28e.jpg
* [Google translation] Fixed- Some wrong control.
https://dlhb.gamebrew.org/3dshomebrew/213848-a22714cf4f2c5ad4f8faee777fbb10df.jpg
* [Speed test] Fixed- Some text color.
<span style="font-size: 15px"><br />
* [Image viewer] Fixed- Some crash.
'''<span style="color: rgb(0, 0, 255)">Select &quot;</span><span style="color: rgb(255, 0, 0)">IDs</span><span style="color: rgb(0, 0, 255)">&quot;.</span>'''</span><br />
* [Setting menu] Fixed- UI design.
https://dlhb.gamebrew.org/3dshomebrew/00000032-jpg
<span style="font-size: 15px"><br />
Tap '''&quot;<span style="color: rgb(0, 0, 255)">Receive</span>&quot;''' tab and press <span style="color: rgb(0, 0, 255)">'''B button'''</span> (or tap'''<span style="color: rgb(0, 0, 255)"> log update</span>''')</span><br />
https://dlhb.gamebrew.org/3dshomebrew/00000030.jpg
<br />
You'll see '''<span style="color: rgb(0, 0, 255)">touch to add this ID </span>and <span style="color: rgb(0, 0, 255)">tap it.</span>'''<br />
https://dlhb.gamebrew.org/3dshomebrew/00000029.jpg
https://dlhb.gamebrew.org/3dshomebrew/00000031.jpg
https://dlhb.gamebrew.org/3dshomebrew/00000033.jpg


  </div>
'''v1.4.2 2020/05/11'''
  <div class="content" id="panel3">
* [App] Fixed- App no longer needs CFW to launch.


<span style="font-size: 15px">'''<span style="color: rgb(0, 179, 89)"><span style="font-size: 26px">'''This guide compatible with''' </span></span>'''<span style="font-size: 26px"><span style="color: rgb(0, 179, 89)">ver </span><span style="color: rgb(0, 0, 255)">1.2.0</span><span style="color: rgb(0, 179, 89)">~</span>'''<span style="color: rgb(255, 0, 0)">1.4.2</span>'''</span>''''''</span><br />
'''v1.4.1 2020/05/05'''
<span style="background-color:AQUA"><span style="font-size: 22px"> ''''''<span style="color: rgb(255, 0, 0)">⚠⚠⚠WARNING⚠⚠⚠</span>'''''' </span></span> '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 0)"><span style="font-size: 22px">I strongly recommend you use the newest version</span></span></span>''' <span style="background-color:AQUA"><span style="font-size: 22px"> '''<span style="color: rgb(255, 0, 0)">⚠⚠⚠WARNING⚠⚠⚠</span>''' </span></span><br />
* [Line] Fixed- Some wrong messages.
<span style="font-size: 15px"><br />
* [Line] Fixed- The app will crash if unsupported sticker was sent.
'''3DS'''<br />
* [Google translation] Fixed- The translation result will be displayed incorrectly in some case.
Tap &quot;<span style="color: rgb(0, 0, 255)">'''Advanced setting'''</span>&quot; and press '''<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)">Y button </span></span>'''and type<span style="font-size: 15px"><span style="color: rgb(0, 0, 255)"> '''ID.'''</span></span></span><br />
* [Google translation] Added- Now, you can translate into over 100 languages.(⁂Some languages(character) will be garbale.)
</span>


  </div>
'''v1.4.0 2020/04/26'''
</div>
* [Line] Added- send a file function.
* [Image viewer] Added- view files on SD card.
* [GAS] Fixed- The problem of losing messages from being sent too quickly have been resolved.


'''If you have any problems fell free to ask me on this discord server.'''<br />
'''v1.3.1 2020/03/31'''
'''<span style="color: rgb(0, 0, 0)">https://discord.gg/bzGnu2a</span>'''<br />
* [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.


== Patch note ==
'''v1.3.0 2020/03/25'''
* [Line] Added- sticker send and receive.


【App】 Ver 1.7.2
'''v1.2.2 2020/05/17'''
*【Line】 Fixed some bugs.
* [Setting] Added- RAM allocation control menu.
*【Vid】 Added debug infomation.
* [Line] Fixed- Now, some errors have been handled.
*【Vid】 Changed initial image size and position
* [Image viewer] Fixed- Now, some errors have been handled.
*【App】 Some minor update to better user experience.
* [Speedtest] Fixed- Now, some errors have been handled.


【App】 Ver 1.7.1
'''v1.2.1 2020/03/03'''
*【Line】 Added- Supported download all files.
* [App] Changed title id to 000400000EC95000.
*【Mup/Vid】 Added- Additional formats support(.ogg).
* [Setting] Added- Fonts setting.
*【App】 Some minor update to better user experience.
* [Setting] Added- Scroll bar.
* [Line] Fixed- Auto update was destroyed.
* [Line] Added- Hide ID.
* [Line] Added- Scroll bar.
* [Image viewer] Fixed- clipboard.
* [Pull requests] Fixed- [https://github.com/Core-2-Extreme/Line_for_3DS/pull/3 #3].


【App】 Ver 1.7.0
'''v1.2.0 2020/01/17'''
*【App】 Fixed- Fonts was improved.
* Fix Some crashes.
*【App】 Added- Video player.
* Fix Some settings do not work.
*【Line】 Added- Now, you can play audio and video sent by user.
* Fix Log download (GAS processing) speed has been improved(GAS update required).
*【Cam】 Fixed- Framerate was improved.
* Add Image viewer.
*【Mup】 Added- Additional formats (like aac) are supported.
*【App】 Some minor update to better user experience.


V1.3.1
[https://github.com/Core-2-Extreme/Line_for_3DS/releases Release notes.]
* 【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
==External links==
* Fix- Some crashes
* GitHub - https://github.com/Core-2-Extreme/Line_for_3DS
* Fix- Some settings do not work
* GBAtemp - https://gbatemp.net/threads/v0-2-0-release-guide-line-for-3ds.539530
* Fix- Log download (GAS processing) speed has been improved(GAS update required)
* Discord - https://discord.gg/EqK3Kpb
* Add- Image viewer

Latest revision as of 04:21, 6 Mayıs 2024

Line for 3DS
Linefor3ds2.png
General
AuthorCore-2-Extreme
TypeOther Apps
Version2.0.0.1
LicenseGPL-3.0
Last Updated2024/01/30
Links
Download
Website
Source
Support Author

Line for 3DS is a LINE client for the 3DS. Developed by LINE, it is an instant messaging, voice and video call application. It allows you to exchange text messages and make calls free of charge by transmitting information in the form of data.

Setup instructions can be found here.

Features

  • Send text.
  • Send stickers. Only package ID 11537, 11538 and 11539 in sticker definitions are supported for now.
  • Send pictures, Up to 37.5MB per picture.
  • Send videos, Up to 37.5MB per video.
  • Send audio, Up to 37.5MB per audio.
  • Send files, Up to 37.5MB per file.
  • View old chat logs, Up to 4000 chat logs.
  • Receive text.
  • Receive stickers. Only package ID 11537, 11538 and 11539 in sticker definitions are displayed for now.
  • Receive pictures. Very large images may not be displayed especially on OLD3DS.
  • Receive audio.
  • Receive videos. Some videos may not be played at full speed or at all especially on OLD3DS.
  • Direct message.
  • Group chat. If Allow bot to join group chat feature in LINE developer page (see Create your LINE BOT step 6) is enabled.
  • Open chat.
  • Voice call.
  • Night mode.

Screenshots

linefor3ds3.png linefor3ds4.png

Media

Line for 3DS (Line for 3DS)

Changelog

v2.0.0.1 2024/01/30

  • Line:
    • Significant changes for internal code, so you need to setup your Google Apps Script again.
    • Setup instructions are updated.
    • 3DS:
      • Chat log wrapping feature has been improved a lot.
      • Auto update feature (for chat logs) are removed temporary.
      • Clear cache feature (for downloaded files) are removed temporary.
    • Google Apps Script:
      • Self tests are added so that it is much easier to trouble shoot.
  • Camera:
    • Supported QR code scan (currently, only for Google Apps Script setup).
  • Video player:
  • Overall:
    • Application folder has been changed from /Line/ to /3ds/Line/ (old folder won't be deleted automatically, but you can delete it if you don't need it).
    • Sub application icon has been added.

v2.0.0 2023/01/27

  • Line:
    • Significant changes for internal code, so you need to setup your Google Apps Script again.
    • Setup instructions are updated.
    • 3DS:
      • Chat log wrapping feature has been improved a lot.
      • Auto update feature (for chat logs) are removed temporary.
      • Clear cache feature (for downloaded files) are removed temporary.
    • Google Apps Script:
      • Self tests are added so that it is much easier to trouble shoot.
  • Camera:
    • Supported QR code scan (currently, only for Google Apps Script setup).
  • Video player:
  • Overall:
    • Application folder has been changed from /Line/ to /3ds/Line/ (old folder won't be deleted automatically, but you can delete it if you don't need it).
    • Sub application icon has been added.

v1.7.2 2021/01/07

  • [Line] Fixed some bugs.
  • [Vid] Added debug infomation.
  • [Vid] Changed initial image size and position.
  • [App] Some minor update to better user experience.

v1.7.1 2020/12/13

  • [Line] Added Supported download all files.
  • [Mup/Vid] Added Additional formats support(.ogg).
  • [App] Some minor update to better user experience.

v1.7.0 2020/12/03

  • [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.6.0 2020/09/12

  • [Line] Fixed- The app will crash if use 'send a file' function.
  • [Line] Fixed- Improved 'send a file' function processing speed.
  • [Line] Added- Search function.
  • [Line] Added- Now, you can receive up to 4000 chat logs.
  • [Music player] Fixed- Improved mp3 play quality.
  • [Music player] Added- Seek function.
  • [Explorer] Added- Now, file will be sorted.

v1.5.2 2020/07/14

  • [Line] Fixed- Adjust some UI.
  • [Camera] Fixed- Some settings will be reset after restarting the camera.
  • [Mic] Fixed- The app will crash if couldn't allocate memory(out of memory).
  • [Music player] Fixed- The app sometimes crash when try playing .mp3 files.
  • [Music player] Added- Shuffle function.

v1.5.1 2020/06/30

  • [App] Fixed- The message will not be displayed correctly if the Line folder does not exist.
  • [Line] Fixed- The message will not be sent if double quote or backslash was included.
  • [Line] Added- Now, you can include new lines in the message.
  • [Music player] Fixed- A memory leak occurs when playing .mp3 files.

v1.5.0 2020/06/22

  • [App] Added- Camera, Mic and Music player.
  • [App] Build with the latest version of ctrlib(v1.9.0)
  • [Line] Fixed- UI design.
  • [Line] Fixed- Made setup steps easier.
  • [Line] Added- Display user/group name and user/group icon.
  • [Google translation] Fixed- Some wrong control.
  • [Speed test] Fixed- Some text color.
  • [Image viewer] Fixed- Some crash.
  • [Setting menu] Fixed- UI design.

v1.4.2 2020/05/11

  • [App] Fixed- App no longer needs CFW to launch.

v1.4.1 2020/05/05

  • [Line] Fixed- Some wrong messages.
  • [Line] Fixed- The app will crash if unsupported sticker was sent.
  • [Google translation] Fixed- The translation result will be displayed incorrectly in some case.
  • [Google translation] Added- Now, you can translate into over 100 languages.(⁂Some languages(character) will be garbale.)

v1.4.0 2020/04/26

  • [Line] Added- send a file function.
  • [Image viewer] Added- view files on SD card.
  • [GAS] Fixed- The problem of losing messages from being sent too quickly have been resolved.

v1.3.1 2020/03/31

  • [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.3.0 2020/03/25

  • [Line] Added- sticker send and receive.

v1.2.2 2020/05/17

  • [Setting] Added- RAM allocation control menu.
  • [Line] Fixed- Now, some errors have been handled.
  • [Image viewer] Fixed- Now, some errors have been handled.
  • [Speedtest] Fixed- Now, some errors have been handled.

v1.2.1 2020/03/03

  • [App] Changed title id to 000400000EC95000.
  • [Setting] Added- Fonts setting.
  • [Setting] Added- Scroll bar.
  • [Line] Fixed- Auto update was destroyed.
  • [Line] Added- Hide ID.
  • [Line] Added- Scroll bar.
  • [Image viewer] Fixed- clipboard.
  • [Pull requests] Fixed- #3.

v1.2.0 2020/01/17

  • Fix Some crashes.
  • Fix Some settings do not work.
  • Fix Log download (GAS processing) speed has been improved(GAS update required).
  • Add Image viewer.

Release notes.

External links

Advertising: