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
No edit summary
Line 2: Line 2:
|title=Line for 3DS
|title=Line for 3DS
|image=Line-for-3DS.png
|image=Line-for-3DS.png
|description=Line for 3DS
|description=Line for 3DS.
|author=Core-2-Extreme
|author=Core-2-Extreme
|lastupdated=2021/01/07
|lastupdated=2021/01/07
Line 8: Line 8:
|version=1.7.2
|version=1.7.2
|license=Mixed
|license=Mixed
|download=https://dlhb.gamebrew.org/3dshomebrew/Line_for_3DSv1.7.2.7z
|download=https://dlhb.gamebrew.org/3dshomebrews/linefor3ds.7z
|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
}}
}}
<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.
 
==Features==
* Send text.
* Send image (v1.4.0).
* Send video(*2) (v1.4.0).
* Send sound(*2) (v1.4.0).
* Send sticker (*1) (v1.3.0).
* Receive text.
* Receive (View) image (v1.2.0).
* Receive (Play) video (v1.7.0).
* Receive (Play) sound (v1.7.0).
* Receive (View) sticker(*1) (v1.3.0).
* View old log (~v1.5.2 Max 300 logs) (v1.6.0~ Max 4000 logs).
* Auto update (v0.2.0).
* Group chat.
* Night mode (v0.2.0).
* Password (v0.3.0).
* Save log to SD card (v0.3.0).


== Line for 3DS ==
==User guide==
The stickers must be included this list [https://dlhb.gamebrew.org/3dshomebrew/sticker_list.pdf sticker_list.pdf].


https://dlhb.gamebrew.org/3dshomebrew/77541203-a9977e80-6ee7-11ea-8479-53aed389ab64.png
Google drive URL will be sent (not embed).


==Supported function:==
===How to setup Google apps script===
Open [https://developers.line.biz/en/ LINE Developers].
* Click log in > Log in with LINE account > Create.
* Type your name, and click Create > Create a Messaging API channel.


* Send text ✅
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds5.png
* Send image ✅ (v1.4.0)
* Send video ✅(*2) (v1.4.0)
* Send sound ✅(*2) (v1.4.0)
* Send sticker ✅ (*1) (v1.3.0)
* Receive text ✅
* Receive (View) image ✅ (v1.2.0)
* Receive (Play) video ✅ (v1.7.0)
* Receive (Play) sound ✅ (v1.7.0)
* Receive (View) sticker ✅(*1) (v1.3.0)
* View old log ✅ (~v1.5.2 Max 300 logs) (v1.6.0~ Max 4000 logs)
* Auto update ✅ (v0.2.0)
* Group chat ✅
* Night mode ✅ (v0.2.0)
* Password ✅ (v0.3.0)
* Save log to SD card ✅ (v0.3.0)


== Official Discord ==
Type each item, and click Create > Messaging API.
* Find Channel access token (long-lived), click Issue.
* Then copy Channel access token (long-lived).


[https://discord.gg/EqK3Kpb https://discord.gg/EqK3Kpb]
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds6.png


== How to use ==
Open [https://www.google.com/intl/en_ALL/drive Google Drive].
* Login, then click New.
* Click Google sheets (1 time) > More > Google apps script (1 time).


'''*1 The stickers must be included this list'''
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds7.png
[https://dlhb.gamebrew.org/3dshomebrew/sticker_list.pdf sticker_list.pdf]


'''*2 Google drive URL will be sent(not embed).'''
Open created Google sheets and copy sheet id (do NOT include "/").
* Open created Google apps script and Insert name ("Line main" in this guide).
* Copy the code below (click to expand) to "Line main", and add your sheet ID, access token, client (3DS's) account name (your favorite name) and script password (if you want).


===How to setup Google apps script===
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds8.png
<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>
{| role="presentation" class="wikitable mw-collapsible mw-collapsed"
  <li class="tab-title active"><a href="#panel1">For Ver 1.7.0</a></li>
| <strong>Code for Ver 1.7.0 ~ 2021/01/24</strong>
  <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>
|<pre>
</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 ACCESS_TOKEN = "Your acces token here";
var OPEN_SHEET_ID = "Your sheet id here";
var OPEN_SHEET_ID = "Your sheet id here";
Line 128: Line 95:
         if (sheet_data == "")
         if (sheet_data == "")
         {
         {
             write_sheet.getRange("A" + sheet_pos).setValue("" + user_name + " : " + message);
             write_sheet.getRange("A" + sheet_pos).setValue("" + user_name + " : " + message);
             write_sheet.getRange("C" + sheet_pos).setValue(msg_id);        
             write_sheet.getRange("C" + sheet_pos).setValue(msg_id);        
             break;
             break;
         }
         }
Line 169: Line 136:
     var write_sheet = spreadsheet.getSheetByName(sheet_id);
     var write_sheet = spreadsheet.getSheetByName(sheet_id);
     var sheet_pos = 1;
     var sheet_pos = 1;
 
     if (!write_sheet)
     if (!write_sheet)
     {
     {
Line 175: Line 142:
         write_sheet = spreadsheet.getSheetByName(sheet_id);
         write_sheet = spreadsheet.getSheetByName(sheet_id);
     }
     }
 
     while (true)
     while (true)
     {
     {
Line 182: Line 149:
         if (sheet_data == msg_id)
         if (sheet_data == msg_id)
         {
         {
             write_sheet.getRange("A" + sheet_pos).setValue("*****This message was unsent by user*****");
             write_sheet.getRange("A" + sheet_pos).setValue("*****This message was unsent by user*****");
             break;
             break;
         }
         }
Line 212: Line 179:
   if(!read_sheet)
   if(!read_sheet)
     return "No such sheet.\nPlease duoble check your ID.";
     return "No such sheet.\nPlease duoble check your ID.";
 
   sheet_start = get_cache_pos(read_sheet);
   sheet_start = get_cache_pos(read_sheet);


Line 676: Line 643:
       return_message = "The audio failed to send. Status code = " + response.getResponseCode() + "\n" + 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();
     content_url = "https://drive.google.com/uc?export=download&id=" + content.getId();
     send_message += "<audio_url>" + content_url + "</audio_url>(" + time + ")";
     send_message += "<audio_url>" + content_url + "</audio_url>(" + time + ")";
Line 705: Line 672:
       return_message = "The video failed to send. Status code = " + response.getResponseCode() + "\n" + 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();
     content_url = "https://drive.google.com/uc?export=download&id=" + content.getId();
     send_message += "<video_url>" + content_url + "</video_url>(" + time + ")";
     send_message += "<video_url>" + content_url + "</video_url>(" + time + ")";
Line 932: Line 899:
       var msg_type = "";
       var msg_type = "";
       var type = "";
       var type = "";
   
 
       try
       try
       {
       {
Line 945: Line 912:
           msg_id = JSON.parse(post_data.postData.contents).events[i].message.id;
           msg_id = JSON.parse(post_data.postData.contents).events[i].message.id;
           user_message = JSON.parse(post_data.postData.contents).events[i].message.text;
           user_message = JSON.parse(post_data.postData.contents).events[i].message.text;
           msg_type = JSON.parse(post_data.postData.contents).events[i].message.type;              
           msg_type = JSON.parse(post_data.postData.contents).events[i].message.type;              
         }
         }
       }
       }
Line 990: Line 957:
       }
       }
       else if(msg_type == "image")
       else if(msg_type == "image")
       {      
       {      
         if(group_id == "Unknown")
         if(group_id == "Unknown")
           user_message = "<image_url>" + get_content_url(msg_id, user_id, msg_type) + "</image_url>";
           user_message = "<image_url>" + get_content_url(msg_id, user_id, msg_type) + "</image_url>";
Line 1,061: Line 1,028:
         if(last == "true")
         if(last == "true")
           sub_data_type = JSON.parse(post_data.postData.contents).data_type;
           sub_data_type = JSON.parse(post_data.postData.contents).data_type;
     
   
         result = upload_content(id, content_data, count, name, last, time, sub_data_type);
         result = upload_content(id, content_data, count, name, last, time, sub_data_type);
       }
       }
       else
       else
         result = "Unknown message type.";
         result = "Unknown message type.";
     
   
       if(result == "Success" && ((data_type == "upload_content" && last == "true") || data_type == "send_sticker" || data_type == "send_text"))
       if(result == "Success" && ((data_type == "upload_content" && last == "true") || data_type == "send_sticker" || data_type == "send_text"))
       {
       {
Line 1,129: Line 1,096:
   return ContentService.createTextOutput(data);
   return ContentService.createTextOutput(data);
}
}
</pre>
|-
|}


</syntaxhighlight>
Then click Publish > Deploy as web app.
* Use the following settings:
** Project version - New.
** Execute the app as - Me (***@gmail.com).
** Who has access to the app - Anyone, even anonymous.
* Finally click Deploy.
* Next click Review permissions and select your google account.
* (If "This app isn't verified/You should avoid this app" shown, click Advanced > Go to Line main (unsafe) > Allow.)
* Copy this URL and save.


  </div>
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds9.png
  <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.
Back to Line Messaging API.
function log_save(message, user_name, write_sheet_name, group_or_user_name, pic_url)
* Find Webhook URL and click edit.
{
* Do NOT type short url here.
    var google_pic_url;
* Type your Google apps script URL, click Update.
    var previous_pic_url;
* Turn on Use webhook, click Verify to check webhook works fine.
    var response;
** Check result and if "success" wasn't displayed, double check your Google apps script URL.
    var sheet_pos = 1;
** (Delete whole url once and re-typing is also good idea.)
    var spreadsheet = SpreadsheetApp.openById(OPEN_SHEET_ID);
    var write_sheet = spreadsheet.getSheetByName(write_sheet_name);


    if (!write_sheet)
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds10.png
    {
        spreadsheet.insertSheet(write_sheet_name);
        write_sheet = spreadsheet.getSheetByName(write_sheet_name);
    }


    sheet_pos = get_cache_pos(write_sheet);
Go to https://free-url-shortener.rb.gy or https://tiny.cc or https://tinyurl.com.
* Then shorten your Google apps script URL.
* Your URL should be http://tinyurl.com/*** or http://tiny.cc/*** or http://rb.gy/***
* This short URL is needed by next section - How to setup (3DS).


    while (true)
===How to setup (3DS)===
    {
Start the application and select Line.
        var sheet_data = write_sheet.getRange("A" + sheet_pos).getValue();
* Set your password (if you don't want use password, enter as empty).
* Press L+R button (or tap Change main URL (short)) and type your short url.
* You MUST type short URL with http:// NOT https:// (don't know why but https:// won't work.).
* This method is compatiable with Ver 1.5.0~1.7.0.


        if (sheet_data == "")
===How to add friend===
        {
On PC or phone (or android emulator):
            write_sheet.getRange("A" + sheet_pos).setValue("" + user_name + " : " + message);
* Find Bot information and scan your bot's QR code or search friends by BOT basic 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 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)
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds11.png
  {
    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 />
On 3DS (this guide compatible with Ver 1.6.0~1.7.0):
<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 />
* Press Y button (or tap Add new ID) and type IDs.
'''<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 />
* Select IDs > Tap Receive and press B button (or tap log update).  
<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>
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds12.png
</div>


== How To Setup (3DS)==
If you get No such sheet error:
<div class="toccolours mw-collapsible mw-collapsed">
* Send "getid" to your 3DS account from your phone/PC account.
Please click here to see the contents:
* Then try again.
<div class="mw-collapsible-content">
* You'll see touch to add this ID and tap it.
'''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 />
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds13.png
Set your password (if you don't want use password, enter as empty).<br />


'''For ver 1.5.0~1.7.0'''
==Screenshots==
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds3.png
https://dlhb.gamebrew.org/3dshomebrews/linefor3ds4.png


==Media==
'''Line for 3DS''' ([https://www.youtube.com/watch?v=5K2fCr0lyoM Line for 3DS]) <br>
<youtube>5K2fCr0lyoM</youtube>


'''<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>''''''
==Changelog==
 
'''Ver 1.7.2'''
Next step there are 2 methods<br />
* [Line] Fixed some bugs.
 
* [Vid] Added debug infomation.
====Method 1 (Easy)====
* [Vid] Changed initial image size and position.
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 />
* [App] Some minor update to better user experience.
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'''.)
 
====Method 2 (Very very very very very very hard)====
 
I do '''NOT''' recommend this method because it is hell ;(<br />
<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.
 
'''Then go to <span style="color: #000000">(Guide)How to add friend</span>.'''<br />
'''If you have any problems fell free to ask me on this discord server.'''<br />
'''<span style="color: rgb(0, 0, 0)">https://discord.gg/bzGnu2a</span>'''<br />
</div>
</div>
== How To Add Friend==
<div class="toccolours mw-collapsible mw-collapsed">
Please click here to see the contents:
<div class="mw-collapsible-content">
 
'''PC''' or '''phone ('''or '''android emulator)<br />
'''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 />
https://dlhb.gamebrew.org/3dshomebrew/c5fce08f621dcbde7f904b526a0dde38-png<br />
 
<ul class="tabs" data-tab>
  <li class="tab-title active"><a href="#panel1">For ver 1.6.0~1.70</a></li>
  <li class="tab-title"><a href="#panel2">For ver 1.5.2~1.5.0</a></li>
  <li class="tab-title"><a href="#panel3">For ver 1.4.2~1.2.0</a></li>
</ul>
<div class="tabs-content">
  <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>
  <div class="content" id="panel2">
<span style="font-size: 15px"><br />
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 />
https://dlhb.gamebrew.org/3dshomebrew/00000029.png
</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 />
<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 />
<span style="font-size: 15px"><br />
'''3DS'''<br />
<br />
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 />
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/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>
  <div class="content" id="panel3">
 
<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 />
<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 />
<span style="font-size: 15px"><br />
'''3DS'''<br />
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 />
</span>
 
  </div>
</div>
 
'''If you have any problems fell free to ask me on this discord server.'''<br />
'''<span style="color: rgb(0, 0, 0)">https://discord.gg/bzGnu2a</span>'''<br />


== Patch note ==
'''Ver 1.7.1'''
* [Line] Added Supported download all files.
* [Mup/Vid] Added Additional formats support(.ogg).
* [App] Some minor update to better user experience.


【App】 Ver 1.7.2
'''Ver 1.7.0'''
*【Line】 Fixed some bugs.
* [App] Fixed Fonts was improved.
*【Vid】 Added debug infomation.
* [App] Added Video player.
*【Vid】 Changed initial image size and position
* [Line] Added Now, you can play audio and video sent by user.
*【App】 Some minor update to better user experience.
* [Cam] Fixed Framerate was improved.
* [Mup] Added Additional formats (like aac) are supported.
* [App] Some minor update to better user experience.


【App】 Ver 1.7.1
'''V1.3.1'''
*【Line】 Added- Supported download all files.
* [App] Ver 1.3.1.
*【Mup/Vid】 Added- Additional formats support(.ogg).
* [Line] Fixed Common occurring [FSUSER_OpenArchive failed] error has been solved.
*【App】 Some minor update to better user experience.
* [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.


【App】 Ver 1.7.0
'''v1.2.0'''
*【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
==External links==
* 【App】 Ver 1.3.1
* GitHub - https://github.com/Core-2-Extreme/Line_for_3DS
* 【Line】 Fixed- Common occurring [FSUSER_OpenArchive failed] error has been solved.
* GBAtemp - https://gbatemp.net/threads/v0-2-0-release-guide-line-for-3ds.539530
* 【Line】 Fixed- Image display now activated by touching the message.
* Discord - https://discord.gg/EqK3Kpb
* 【Setting】 Fixed- Setting menu framerate now improved.
* 【Image viewer】 Fixed- Image display speed now improved.


v1.2.0
[[Category:3DS homebrew applications]]
* Fix- Some crashes
[[Category:Other 3DS homebrew applications]]
* Fix- Some settings do not work
* Fix- Log download (GAS processing) speed has been improved(GAS update required)
* Add- Image viewer

Revision as of 16:39, 10 December 2021

Line for 3DS
File:Line-for-3DS.png
General
AuthorCore-2-Extreme
TypeOther Apps
Version1.7.2
LicenseMixed
Last Updated2021/01/07
Links
Download
Website
Source

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.

Features

  • Send text.
  • Send image (v1.4.0).
  • Send video(*2) (v1.4.0).
  • Send sound(*2) (v1.4.0).
  • Send sticker (*1) (v1.3.0).
  • Receive text.
  • Receive (View) image (v1.2.0).
  • Receive (Play) video (v1.7.0).
  • Receive (Play) sound (v1.7.0).
  • Receive (View) sticker(*1) (v1.3.0).
  • View old log (~v1.5.2 Max 300 logs) (v1.6.0~ Max 4000 logs).
  • Auto update (v0.2.0).
  • Group chat.
  • Night mode (v0.2.0).
  • Password (v0.3.0).
  • Save log to SD card (v0.3.0).

User guide

The stickers must be included this list sticker_list.pdf.

Google drive URL will be sent (not embed).

How to setup Google apps script

Open LINE Developers.

  • Click log in > Log in with LINE account > Create.
  • Type your name, and click Create > Create a Messaging API channel.

linefor3ds5.png

Type each item, and click Create > Messaging API.

  • Find Channel access token (long-lived), click Issue.
  • Then copy Channel access token (long-lived).

linefor3ds6.png

Open Google Drive.

  • Login, then click New.
  • Click Google sheets (1 time) > More > Google apps script (1 time).

linefor3ds7.png

Open created Google sheets and copy sheet id (do NOT include "/").

  • Open created Google apps script and Insert name ("Line main" in this guide).
  • Copy the code below (click to expand) to "Line main", and add your sheet ID, access token, client (3DS's) account name (your favorite name) and script password (if you want).

linefor3ds8.png

Then click Publish > Deploy as web app.

  • Use the following settings:
    • Project version - New.
    • Execute the app as - Me (***@gmail.com).
    • Who has access to the app - Anyone, even anonymous.
  • Finally click Deploy.
  • Next click Review permissions and select your google account.
  • (If "This app isn't verified/You should avoid this app" shown, click Advanced > Go to Line main (unsafe) > Allow.)
  • Copy this URL and save.

linefor3ds9.png

Back to Line Messaging API.

  • Find Webhook URL and click edit.
  • Do NOT type short url here.
  • Type your Google apps script URL, click Update.
  • Turn on Use webhook, click Verify to check webhook works fine.
    • Check result and if "success" wasn't displayed, double check your Google apps script URL.
    • (Delete whole url once and re-typing is also good idea.)

linefor3ds10.png

Go to https://free-url-shortener.rb.gy or https://tiny.cc or https://tinyurl.com.

How to setup (3DS)

Start the application and select Line.

  • Set your password (if you don't want use password, enter as empty).
  • Press L+R button (or tap Change main URL (short)) and type your short url.
  • You MUST type short URL with http:// NOT https:// (don't know why but https:// won't work.).
  • This method is compatiable with Ver 1.5.0~1.7.0.

How to add friend

On PC or phone (or android emulator):

  • Find Bot information and scan your bot's QR code or search friends by BOT basic ID.

linefor3ds11.png

On 3DS (this guide compatible with Ver 1.6.0~1.7.0):

  • Press Y button (or tap Add new ID) and type IDs.
  • Select IDs > Tap Receive and press B button (or tap log update).

linefor3ds12.png

If you get No such sheet error:

  • Send "getid" to your 3DS account from your phone/PC account.
  • Then try again.
  • You'll see touch to add this ID and tap it.

linefor3ds13.png

Screenshots

linefor3ds3.png linefor3ds4.png

Media

Line for 3DS (Line for 3DS)

Changelog

Ver 1.7.2

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

Ver 1.7.1

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

Ver 1.7.0

  • [App] Fixed Fonts was improved.
  • [App] Added Video player.
  • [Line] Added Now, you can play audio and video sent by user.
  • [Cam] Fixed Framerate was improved.
  • [Mup] Added Additional formats (like aac) are supported.
  • [App] Some minor update to better user experience.

V1.3.1

  • [App] Ver 1.3.1.
  • [Line] Fixed Common occurring [FSUSER_OpenArchive failed] error has been solved.
  • [Line] Fixed Image display now activated by touching the message.
  • [Setting] Fixed Setting menu framerate now improved.
  • [Image viewer] Fixed Image display speed now improved.

v1.2.0

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

External links

Advertising: