565 lines
16 KiB
TypeScript
Raw Normal View History

2023-11-23 16:33:21 +08:00
import BusinessTypeSetting from "@/_BusinessTypeSetting/BusinessTypeSetting";
import liff from "@line/liff";
/**
* Line工具
* @doc https://developers.line.biz/en/docs/messaging-api/message-types
*/
export class LineTools {
//#region Lifecycle
public static async onLoad(): Promise<void> {
const hasToken: boolean = location.search.includes("token=") || location.hash.includes("token=");
if (hasToken) {
return;
}
await LineTools.init();
const isLoggedIn: boolean = await LineTools.checkLogin();
if (!isLoggedIn) {
await LineTools.login();
return;
}
console.debug(`[Line] Line is Login`);
}
//#endregion
//#region Custom
public static GetAccessToken(): string {
const accessToken: string = liff.getAccessToken();
return accessToken;
}
//#endregion
//#region FriendShip
/** 確認是否加官方賬號好友 */
public static async CheckAddFriend(): Promise<boolean> {
const ans: boolean = (await liff.getFriendship()).friendFlag;
return ans;
}
//#endregion
//#region Custom Message
/**
* Text message
* @param {string} text Message text. Max character limit: 5000
* @doc https://developers.line.biz/en/reference/messaging-api/#text-message
* @example LineTools.MakeText("Hello, world");
*/
public static async MakeText(text: string): Promise<boolean> {
if (!text) {
return false;
}
const data: any[] = [{
"type": "text",
"text": text
}];
return await LineTools._sendMessages(data);
}
/**
* Text message
* @deprecated
* @param {string} text Message text. Max character limit: 5000
* @doc https://developers.line.biz/en/reference/messaging-api/#text-message
* @LINE_Emoji https://developers.line.biz/en/docs/messaging-api/emoji-list/#line-emoji-definitions
* @example LineTools.MakeText("Hello, world");
*/
public static async MakeTextEmoji(text: string): Promise<boolean> {
if (!text) {
return false;
}
const data: any[] = [{
"type": "text",
"text": "$$$ LINE emoji",
"emojis": [
{
"index": 0,
"productId": "5ac21a8c040ab15980c9b43f",
"emojiId": "004"
},
{
"index": 1,
"productId": "5ac21a8c040ab15980c9b43f",
"emojiId": "001"
},
{
"index": 2,
"productId": "5ac21a8c040ab15980c9b43f",
"emojiId": "025"
}
]
}];
return await LineTools._sendMessages(data);
}
/**
* Sticker message
* @param packageId Package ID for a set of stickers. For information on package IDs
* @param stickerId Sticker ID. For a list of sticker IDs for stickers that can be sent with the Messaging API
* @param isAnim isAnim
* @doc https://developers.line.biz/en/reference/messaging-api/#audio-message
* @example LineTools.MakeSticker(26162, 505588336, true);
*/
public static async MakeSticker(packageId: string, stickerId: string, isAnim: boolean = false): Promise<boolean> {
if (!packageId || !stickerId) {
return false;
}
let pngtype: string = "";
if (isAnim) {
pngtype = "/IOS/sticker@2x.png";
} else {
pngtype = "/IOS/sticker_animation@2x.png";
}
const data: any[] = [{
"type": "template",
"altText": "Sticker",
"template": {
"type": "image_carousel",
"columns": [{
"imageUrl": "https://stickershop.line-scdn.net/stickershop/v1/sticker/" + stickerId + pngtype,
"action": {
"type": "uri",
"uri": "line://shop/sticker/detail/" + packageId
}
}]
}
}];
return await LineTools._sendMessages(data);
}
/**
* Image message
* @param {string} originalContentUrl Image URL (Max character limit: 2000) HTTPS over TLS 1.2 or later JPEG or PNG Max image size: No limits Max file size: 10 MB
* @param {string} previewImageUrl Preview image URL (Max character limit: 2000) HTTPS over TLS 1.2 or later JPEG or PNG Max image size: No limits Max file size: 1 MB
* @doc https://developers.line.biz/en/reference/messaging-api/#image-message
* @example LineTools.MakeImage("https://example.com/original.jpg", "https://example.com/preview.jpg");
*/
public static async MakeImage(originalContentUrl: string, previewImageUrl: string = originalContentUrl): Promise<boolean> {
if (!originalContentUrl) {
return false;
}
const data: any[] = [{
"type": "image",
"originalContentUrl": originalContentUrl,
"previewImageUrl": previewImageUrl
}];
return await LineTools._sendMessages(data);
}
/**
* Video message
* If the video isn't playing properly, make sure the video is a supported file type and the HTTP server hosting the video supports HTTP range requests.
* @param {string} originalContentUrl URL of video file (Max character limit: 2000) HTTPS over TLS 1.2 or later mp4 Max file size: 200 MB
* @param {string} previewImageUrl URL of preview image (Max character limit: 2000) HTTPS over TLS 1.2 or later JPEG or PNG Max file size: 1 MB
* @doc https://developers.line.biz/en/reference/messaging-api/#video-message
*/
public static async MakeVideo(originalContentUrl: string, previewImageUrl: string): Promise<boolean> {
if (!originalContentUrl || !previewImageUrl) {
return false;
}
const data: any[] = [{
"type": "video",
"originalContentUrl": originalContentUrl,
"previewImageUrl": previewImageUrl
}];
return await LineTools._sendMessages(data);
}
/**
* Audio message
* @param {string} originalContentUrl URL of audio file (Max character limit: 2000) HTTPS over TLS 1.2 or later m4a Max file size: 200 MB
* @param {number} duration Length of audio file (milliseconds)
* @doc https://developers.line.biz/en/reference/messaging-api/#audio-message
*/
public static async MakeAudio(originalContentUrl: string, duration: number = 60000): Promise<boolean> {
if (!originalContentUrl) {
return false;
}
const data: any[] = [{
"type": "audio",
"originalContentUrl": originalContentUrl,
"duration": duration
}];
return await LineTools._sendMessages(data);
}
/**
* Buttons template
* @param altText Alternative text. When a user receives a message, it will appear as an alternative to the template message in the notification or chat list of their device. Max character limit: 400
* @param thumbnailImageUrl Image URL (Max character limit: 2,000) HTTPS over TLS 1.2 or later JPEG or PNG Max width: 1024px Max file size: 10 MB
* @param title Title Max character limit: 40
* @param text Message text Max character limit: 160 (no image or title) Max character limit: 60 (message with an image or title)
* @param defaultAction Action when image, title or text area is tapped.
* @param actions Action when tapped Max objects: 4
* @param imageAspectRatio rectangle: 1.51:1
* @param imageAspectRatio square: 1:1
* @param imageSize cover: The image fills the entire image area. Parts of the image that do not fit in the area are not displayed.
* @param imageSize contain: The entire image is displayed in the image area. A background is displayed in the unused areas to the left and right of vertical images and in the areas above and below horizontal images.
* @doc https://developers.line.biz/en/reference/messaging-api/#buttons
*/
public static async MakeTemplate(altText: string, thumbnailImageUrl: string, title: string, text: string, defaultAction: any = null, actions: any[] = [], imageAspectRatio: string = "square", imageSize: string = "cover"): Promise<boolean> {
if (actions.length === 0) {
return false;
}
const data: any[] = [{
"type": "template",
"altText": altText,
"template": {
"type": "buttons",
"thumbnailImageUrl": thumbnailImageUrl,
"imageAspectRatio": imageAspectRatio,
"imageSize": imageSize,
"title": title,
"text": text
}
}];
if (defaultAction) {
data["defaultAction"] = defaultAction;
// data["defaultAction"] = {
// "type": "uri",
// "label": "View detail",
// "uri": "http://example.com/page/123"
// };
}
if (actions) {
data["actions"] = actions;
// data["actions"] = [{
// "type": "uri",
// "label": "立即玩",
// "uri": "https://liff.line.me/1657713613-we8Gk929"
// }];
}
return await LineTools._sendMessages(data);
}
//#region SendImageTemplate
/**
* MeProfile
* @param altText Alternative text. When a user receives a message, it will appear as an alternative to the template message in the notification or chat list of their device. Max character limit: 400
* @param text Message text Max character limit: 160 (no image or title) Max character limit: 60 (message with an image or title)
* @simulator https://developers.line.biz/flex-simulator/
*/
public static async MakeImageTemplate(altText: string, text?: string): Promise<boolean> {
let data: any[] = [{
"type": "flex",
"altText": altText,
"contents":
{
"type": "bubble",
"size": "giga",
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "image",
"url": "https://scontent.ftpe8-3.fna.fbcdn.net/v/t39.30808-6/313961998_137927952344738_4107082514038089088_n.jpg?stp=dst-jpg_p526x296&_nc_cat=111&ccb=1-7&_nc_sid=730e14&_nc_ohc=3jmqu3srEAYAX9drH1H&_nc_ht=scontent.ftpe8-3.fna&oh=00_AfDe36ZgvY6aqmN3nge4Fmw9ZGuOwxdS5fj9eAMLe6wtBg&oe=63A4C088",
"size": "full",
"aspectMode": "cover"
}
],
"paddingAll": "none"
},
"action": {
"type": "uri",
"label": "action",
"uri": `https://liff.line.me/${BusinessTypeSetting.UseLiffID}}`
}
}
}];
if (text) {
data[0].contents.body.contents.push({
"type": "text",
"text": text,
"size": "md",
"align": "center",
"color": "#0000FF",
"margin": "50px",
"gravity": "center",
"offsetBottom": "20px"
});
}
return await LineTools._sendMessages(data);
}
//#endregion
//#region MakeShareBigWinGame
/**
*
* @param {number} slotID
* @param {number} ratio
* @param {string} altText
* @param {string} text
* @param {string} btnText
* @param {string} btnUrl
*/
public static async MakeShareBigWinGame(slotID: number, ratio: number, altText: string, text: string, btnText: string, ...param: any[]): Promise<boolean> {
text = String.Format(text, ratio);
let data: any[] = [{
"type": "flex",
"altText": altText,
"contents":
{
"type": "bubble",
"size": "mega",
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "image",
"url": `${BusinessTypeSetting.UseDownloadUrl}game/${slotID}/s`,
"position": "absolute",
"offsetTop": "11%",
"offsetStart": "71%",
"size": "75px"
},
{
"type": "image",
"url": `${BusinessTypeSetting.UsePatch}shared/img/LineShareUI/ShareAward01.png`,
"size": "full",
"aspectRatio": "20:13",
"aspectMode": "cover"
}
],
"paddingAll": "none"
},
{
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": text,
"weight": "bold",
"size": "md",
"align": "center",
"wrap": true
}
],
"margin": "xl"
}
],
"paddingAll": "none"
},
"footer": {
"type": "box",
"layout": "vertical",
"spacing": "sm",
"contents": [
{
"type": "button",
"style": "link",
"height": "sm",
"action": {
"type": "uri",
"label": btnText,
"uri": `https://line.me/R/app/${BusinessTypeSetting.UseLiffID}/?gamein=${slotID}`
}
}
],
"flex": 0
},
"action": {
"type": "uri",
"label": btnText,
"uri": `https://line.me/R/app/${BusinessTypeSetting.UseLiffID}/?gamein=${slotID}`
}
}
}];
return await LineTools._sendMessages(data);
}
//#endregion
//#region SelfProfile
/**
* MeProfile
* @simulator https://developers.line.biz/flex-simulator/
*/
public static async MeProfile(): Promise<void> {
const altText: string = "立即玩爆機娛樂城";
// eslint-disable-next-line @typescript-eslint/typedef
liff.getProfile().then(function (profileData): void {
let statusMessage: string = profileData.statusMessage ?? "";
if (statusMessage.length > 60) {
statusMessage = "Status Message is to long! Max 60 words";
}
const data: any[] = [
{
"type": "flex",
"altText": altText,
"contents":
{
"type": "bubble",
// "size": "giga",
"size": "kilo",
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "image",
"url": profileData.pictureUrl,
"size": "full",
"aspectMode": "cover"
},
{
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": profileData.displayName,
"weight": "bold",
"size": "lg",
"margin": "lg"
},
{
"type": "text",
"text": statusMessage,
"size": "sm",
"margin": "md",
"wrap": true
}
],
"paddingEnd": "5%",
"paddingStart": "5%"
},
{
"type": "text",
"text": "立即玩",
"size": "md",
"align": "center",
"color": "#0000FF",
"margin": "50px",
"gravity": "center",
"offsetBottom": "20px"
}
],
"paddingAll": "none"
},
"action": {
"type": "uri",
"label": "action",
"uri": `https://liff.line.me/${BusinessTypeSetting.UseLiffID}`
}
}
}
];
LineTools._sendMessages(data);
}).catch(function (error: any): void {
alert(`[Line] Failed to getProfile: \n${error}`);
});
}
public static async GetLineProfile(): Promise<any> {
return await liff.getProfile();
}
//#endregion
/**
*
* @param {SendMessagesParams} messages
* @param {boolean} isMultiple If you set the isMultiple property to true, the user can select multiple message recipients in the target picker. If you set it to false, the user can select only one friend as the message recipient. The default value is true.
*/
private static async _sendMessages(messages: any[], isMultiple: boolean = true): Promise<boolean> {
// 這邊是為了防止token過期
const isLoggedIn: boolean = await LineTools.checkLogin();
if (!isLoggedIn) {
await LineTools.login();
}
let isSuccess: boolean = false;
if (liff.isApiAvailable("shareTargetPicker")) {
try {
const res = await liff.shareTargetPicker(messages, { isMultiple: isMultiple });
if (res) {
if (res.status === "success") {
isSuccess = true;
console.log(`[Line] 分享成功`);
} else {
console.error(`[Line] 分享失敗: \n${JSON.stringify(res)}`);
}
} else {
console.log(`[Line] 分享取消`);
}
} catch (error) {
console.error(`[Line] Failed to launch ShareTargetPicker: \n${error}`);
}
} else {
alert("[Line] 你的 LINE App 暫時不支援 Share Target Picker");
}
return isSuccess;
}
private static sleep(ms: any): Promise<any> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
private static async init(): Promise<void> {
const liffId: string = BusinessTypeSetting.UseLiffID;
const myLIFF_STORE: Object = LineTools.getLIFF_STORE();
await liff.init({ liffId: liffId });
LineTools.setLIFF_STORE(myLIFF_STORE);
}
private static async checkLogin(): Promise<boolean> {
let isLoggedIn: boolean = liff.isLoggedIn();
try {
const a = await liff.getProfile();
} catch (error) {
isLoggedIn = false;
}
return isLoggedIn;
}
private static async login(): Promise<void> {
if (liff.isInClient()) {
liff.login({ redirectUri: parent.location.href });
} else {
const search: string = location.search ? location.search : location.hash;
const callbackURL: string = BusinessTypeSetting.UsePatch + "/" + search;
liff.login({ redirectUri: callbackURL });
}
}
private static getLIFF_STORE(): Object {
const LIFF_STORE: Object = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.includes("LIFF_STORE")) {
LIFF_STORE[key] = localStorage.getItem(key);
}
}
return LIFF_STORE;
}
private static setLIFF_STORE(LIFF_STORE: Object) {
for (let i = 0, keys = Object.keys(LIFF_STORE); i < keys.length; i++) {
const key = keys[i];
const item = LIFF_STORE[key];
localStorage.setItem(key, item);
}
}
}