Showing posts with label facebook. Show all posts
Showing posts with label facebook. Show all posts

Monday, February 2, 2026

өөрийн веб хуудасыг facebook share яаж хийх вэ? next.js ашиглан


* /api/og-images/route.tsx

import { ImageResponse } from 'next/og';

export async function GET(request: Request) {
    // URL parse
    const { searchParams } = new URL(request.url);
    // ?title=Hello
    const title = searchParams.get('title') ?? 'Default гарчиг';

    return new ImageResponse(
        <div style={{ fontSize: 60, background: '#000', color: '#fff' }}>
            {title || `Миний гарчиг`}
        </div>,
        {
            width: 1200,
            height: 600,
        },
    );
}

* page.tsx

 import { Metadata } from 'next';

import HomeSections from './sections';

export async function generateMetadata({
    params,
}: {
    params: { slug: string };
}): Promise<Metadata> {
    // const article = await getArticle(params.slug); // DB / API

    return {
        title: `article.title`,
        description: `article.summary`,
        openGraph: {
            title: `article.title`,
            description: `article.summary`,
            url: `https://example.com/article/${params.slug}`,
            siteName: 'My Website',
            images: [
                {
                    url: `/api/og-images?title=Hello`,
                    width: 1200,
                    height: 600,
                },
            ],
            locale: 'mn_MN',
            type: 'article',
        },
    };
}

export default function HomePage() {
    return <HomeSections />;
}


* sections.tsx

'use client';

import { postRequestOptions } from '@/libs/helpers/networker';
import { useEffect, useState } from 'react';

export default function HomeSections() {
    const [accessToken, setAccessToken] = useState<string | null>(null);

    useEffect(() => {
        // Facebook SDK script нэмэх
        const loadFbSdk = () =>
            new Promise<void>((resolve) => {
                ((d, s, id) => {
                    const fjs = d.getElementsByTagName(s)[0];
                    if (d.getElementById(id)) return resolve();
                    const js = d.createElement(s) as HTMLScriptElement;
                    js.id = id;
                    js.src = 'https://connect.facebook.net/en_US/sdk.js';
                    js.onload = () => resolve();
                    d.body.appendChild(js);
                })(document, 'script', 'facebook-jssdk');
            });

        // SDK init
        loadFbSdk().then(() => {
            (window as any).FB.init({
                appId: process.env.NEXT_PUBLIC_FB_APP_ID,
                cookie: true,
                xfbml: false,
                version: 'v19.0',
            });
        });
    }, []);

    // Login function
    const fbLogin = () => {
        (window as any).FB.login(
            (response: any) => {
                if (response.authResponse) {
                    const accessToken = response.authResponse.accessToken;
                    console.log('User Access Token:', accessToken);
                    // Backend руу дамжуулахад ашиглана
                    setAccessToken(accessToken);
                } else {
                    console.log(
                        'User cancelled login or did not fully authorize.',
                    );
                    setAccessToken(null);
                }
            },
            { scope: 'public_profile,email,user_posts' },
        ); // user_posts scope зөвхөн тест токен дээр ажиллана
    };

    // Share function
    const shareArticle = (articleUrl: string) => {
        // const linkedMessage = `${articleUrl}`;
        // const shareUrl =
        //     'https://www.facebook.com/sharer/sharer.php?quote="hello"&u=' +
        //     encodeURIComponent(linkedMessage);
        // window.open(shareUrl, '_blank', 'width=600,height=400');
        const url = encodeURIComponent(`https://news.mn/r/2847199/`);
        window.open(
            `https://www.facebook.com/sharer/sharer.php?u=${url}`,
            '_blank',
            'width=600,height=500',
        );
    };

    const checkSharedPosts = async (articleUrl: string) => {
        fetch(
            '/api/check-fb-posts',
            postRequestOptions({
                accessToken,
                siteUrl: articleUrl,
            }),
        )
            .then((res) => {
                return res.json();
            })
            .then((data) => {
                console.log('checkSharedPosts:', data);
            })
            .catch((err) => {
                console.error('checkSharedPosts:', err);
            });
    };

    return (
        <div className="flex flex-col justify-center">
            <div>
                {accessToken ? (
                    <div>Нэвтэрсэн :)</div>
                ) : (
                    <div>Нэвтрээгүй (:</div>
                )}
            </div>
            <button
                className="bg-indigo-700 text-gray-300 text-md p-4 cursor-pointer"
                onClick={fbLogin}
            >
                Login with Facebook
            </button>
            <button
                className="bg-indigo-700 text-gray-300 text-md p-4 cursor-pointer"
                onClick={() =>
                    shareArticle('https://itissandboxbro.duckdns.org/admin')
                }
            >
                Share content on Facebook
            </button>
            <button
                className="bg-indigo-700 text-gray-300 text-md p-4 cursor-pointer"
                onClick={() =>
                    checkSharedPosts('https://itissandboxbro.duckdns.org/admin')
                }
            >
                Check out the posts shared on Facebook
            </button>
        </div>
    );
}



facebook дээр post share хийсэн сүүлийн 10 дотроос миний холбоос message дотор байна уу? шалгах код жишээ

 FRONT-END next.js

const checkSharedPosts = async (articleUrl: string) => {
        fetch(
            '/api/check-fb-posts',
            postRequestOptions({
                accessToken,
                siteUrl: articleUrl,
            }),
        )
            .then((res) => {
                return res.json();
            })
            .then((data) => {
                console.log('checkSharedPosts:', data);
            })
            .catch((err) => {
                console.error('checkSharedPosts:', err);
            });
    };

BACK-END nest.js

@Post('/check-fb-posts')

    async checkFbPosts(@Body() body: { accessToken: string; siteUrl: string }) {
        const { accessToken, siteUrl } = body;

        // Graph API руу сүүлийн 10 пост авах
        const url =
            `https://graph.facebook.com/me/posts` +
            `?fields=id,message,permalink_url,created_time&limit=10&access_token=${accessToken}`;
        const res = await fetch(url);
        const data = await res.json();

        // Сүүлд 10 постоос таны сайтын URL байгаа эсэхийг шалгах
        const found = data.data.find(
            (post: any) =>
                (post.message && post.message.includes(siteUrl)) ||
                (post.permalink_url && post.permalink_url.includes(siteUrl)),
        );

        return { founded: !!found, posts: data.data };
    }

Facebook 2018 оноос хойш user timeline дээр API-аар шууд post share хийхийг ХОРИГЛОСОН publish_actions гэдэг permission бүрмөсөн устсан

code:

nest.js дотор @Post('fb-post') 
    async fbPosts(@Body() body: { accessToken: string; message: string }) {
        const res = await fetch(`https://graph.facebook.com/me/feed`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                message: body.message,
                access_token: body.accessToken,
            }),
        });
        
        const result = await res.json();
       
        return { shared: true, posts: result };
    } функцээр facebook share хийх үед {
  message: `(#200) If posting to a group, requires app being installed in the group, and \\
          either publish_to_groups permission with user token, or both pages_read_engagement \\
          and pages_manage_posts permission with page token; If posting to a page, \\
          requires both pages_read_engagement and pages_manage_posts as an admin with \\
          sufficient administrative permission`,
  type: "OAuthException",
  code: 200,
  fbtrace_id: "Ano23PuN6w2b7GF1CfoQiz1",
} алдаа гараад байх юм яаж зөв ажиллуулах вэ? би өөрийн үүсгэсэн facebook app руугаа development mode дээр зүгээр share post тест хийж үзэх гэж байгаа юм! 

Яагаад энэ алдаа гарч байна вэ?

Чи одоо:

POST https://graph.facebook.com/me/feed

гэж дуудаж байна. me/feed гэдэг нь хаана post хийхийг access token чинь тодорхойлно гэсэн үг.

Facebook ингэж ойлгож байна 👇

  • Group дээр post хийх гэж байна уу?

  • Page дээр post хийх гэж байна уу?

  • User-ийн own timeline дээр post хийх гэж байна уу?

Тэгээд permission хангалтгүй болохоор (#200) алдаа өгч байна.


1️⃣ Хэрвээ ЧИ ЗӨВХӨН ӨӨРИЙН TIMELINE дээр post хийх гэж байгаа бол

Facebook 2018 оноос хойш user timeline дээр API-аар шууд post хийхийг ХОРИГЛОСОН.

👉 publish_actions гэдэг permission бүрмөсөн устсан
👉 me/feed → user timeline = ❌ API-аар БОЛОХГҮЙ

Тиймээс:

❌ API-аар “өөрийн timeline дээр пост хийнэ” гэдэг боломж байхгүй болсон


2️⃣ Group дээр post хийх бол (чи энэ алдааг авч байгаа учир магадлал өндөр)

Шаардлага:

  • App чинь тэр групп дээр installed байх

  • Permission:

    • publish_to_groups

  • Token:

    • User access token

Яаж шалгах вэ?

  1. Group → Settings

  2. Apps → Add your app

  3. App Review дээр publish_to_groups-ийг Advanced Access болгосон байх

⚠️ Development mode дээр:

  • Зөвхөн app admin / tester групп дээр post хийж чадна


3️⃣ Page дээр post хийх бол (хамгийн найдвартай зам)

Зөв setup:

Permission-ууд:

pages_read_engagement pages_manage_posts

Алхам:

  1. User token авна (admin account)

  2. Page access token авна

  3. Page ID-гаар post хийнэ

POST https://graph.facebook.com/{PAGE_ID}/feed
body: JSON.stringify({ message: body.message, access_token: PAGE_ACCESS_TOKEN, })

📌 me/feed биш {page-id}/feed


4️⃣ Чиний одоогийн кодыг яаж “зөв” болгох вэ?

Page post хийх хувилбар (ажиллана):

@Post('fb-post') async fbPosts(@Body() body: { pageAccessToken: string; message: string }) { const PAGE_ID = '1234567890'; const res = await fetch( `https://graph.facebook.com/${PAGE_ID}/feed`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: body.message, access_token: body.pageAccessToken, }), }, ); const result = await res.json(); return result; }

Sunday, March 10, 2019

facebook instant game custom server communication Nodejs, PHP

game.js

const BACKEND_URL = "https://localhost/quizbattle";
const BACKEND_URL_API = BACKEND_URL + "/api";
var game = {};
var gameData = {};
var gameOptions = {
    width: 0,
    height: 0,
    backgroundColor: 0xFFFFFF,
    localStorageName: "__ab__"
};
var gameStuff = {
    playerId: "",
    playerName: "",
    playerPic: "",
    locale: ""
};

window.onload = function () {
    $('#home-section').hide();

    FBInstant.initializeAsync().then(function () {
        FBInstant.startGameAsync().then(function () {
            gameStuff.playerId = FBInstant.player.getID();
            gameStuff.playerName = FBInstant.player.getName();
            gameStuff.playerPic = FBInstant.player.getPhoto();
            gameStuff.locale = FBInstant.getLocale();
            gameStuff.contextType = FBInstant.context.getType();

            gameOptions.height = window.innerHeight;
            gameOptions.width = window.innerWidth / 2;

            $('#u-photo-field').attr('src', gameStuff.playerPic);
            $('#u-name-field').html(gameStuff.playerName);
            $('#context-type').html(gameStuff.contextType);

            $('#home-section').show();
           
            loadingInitialData();
        });
    });
};

function getOrCreateContextId() {
    var contextType = FBInstant.context.getType();
    var contextId = FBInstant.context.getID();
   
    console.log(contextType);
    if (contextType == 'SOLO') {
        contextId = FBInstant.player.getID() + '_SOLO';
    }
    return contextId;
}

function showMessage(div, message) {
    document.getElementById(div).innerHTML = message;
}

function loadingInitialData() {
    var contextId = getOrCreateContextId();

    FBInstant.player.getSignedPlayerInfoAsync(contextId)
            .then(function (signedPlayerInfo) {
                // Requesting data from backend passing the signature as an argument
                // The backend APP can retrieve contextId from the signature
                var signature = signedPlayerInfo.getSignature();
                return new backendClient(BACKEND_URL_API).load(contextId, signature);
            })
            .then(function (response) {
                // Got successful response from backend
                console.log('Loaded from backend', response);
                if (response.empty) {
                    showMessage('test', 'No data');
                } else {
                    showMessage('test', JSON.stringify(response.data));
                }
            }.bind(this))
            .catch(function (error) {
                // Got error response from backend
                console.error('Not loaded from backend', error);
                showMessage('error-messages', 'Error loading backend data:' + error.message);
            }.bind(this));
}

function singlePlayBoot() {

}

function multiPlayBoot() {

}

function leaderBoardBoot() {

}

function helpBoot() {

}


node.js server side


var pg = require('pg');
var crypto = require('crypto-js');

module.exports = function(app) {

app.post('/get-match', function(request, response) {
        var signature = request.body.signature;
       
        var isValid = validate(signature);
       
        if (isValid) {
            var contextId = getEncodedData(signature);
            loadMatchDataAsync(contextId)
            .then(function(result){
                if (result) {
                    response.json({'success':true, 'contextId':contextId, 'empty': false, 'data':result});
                } else {
                    response.json({'success':true, 'contextId':contextId, 'empty': true});
                }
            })
            .catch(function(err){
                response.json({'success':false, 'error':err});
            });
        } else {
            console.log('encoded data', getEncodedData(signature));
            response.json({'success':false, 'error':'invalid signature'});
        }
       
    })

loadMatchDataAsync = function(contextId) {
        return new Promise(function(resolve, reject){
            pg.connect(process.env.DATABASE_URL, function(err, client, done) {
                client.query('SELECT * FROM matches WHERE context = $1::text', [contextId], function(err, result) {
                    done();
                    if (err) {
                        reject(err);
                    }
                    if (result.rows.length > 0) {
                        resolve(result.rows[0].data);
                    } else {
                        resolve();
                    }
                });
            });
        });
    };
   
    validate = function(signedRequest) {
        try{
           
            var firstpart = signedRequest.split('.')[0];
            var replaced = firstpart.replace(/-/g, '+').replace(/_/g, '/');
            var signature = crypto.enc.Base64.parse(replaced).toString();
            const dataHash = crypto.HmacSHA256(signedRequest.split('.')[1], process.env.APP_SECRET).toString();
            var isValid = signature === dataHash;
            if (!isValid) {
                console.log('Invalid signature');
                console.log('firstpart', firstpart);
                console.log('replaced ', replaced);
                console.log('Expected', dataHash);
                console.log('Actual', signature);
            }
           
            return isValid;
        } catch (e) {
            return false;
        }
    };
   
    getEncodedData = function(signedRequest) {
        try {
           
            const json = crypto.enc.Base64.parse(signedRequest.split('.')[1]).toString(crypto.enc.Utf8);
            const encodedData = JSON.parse(json);
           
            /*
            Here's an example of encodedData can look like
            {
                algorithm: 'HMAC-SHA256',
                issued_at: 1520009634,
                player_id: '123456789',
                request_payload: 'backend_save'
            }
            */
           
            return encodedData.request_payload;
        } catch (e) {
            return null;
        }
    };
}


PHP server side


private function _validate($signedRequest) {
        try {
            list($encoded_signature, $payload) = explode('.', $signedRequest, 2);

            // decode the data
            $signature = base64_url_decode($encoded_signature);
            //$data = json_decode(base64_url_decode($payload), true);
           
            // confirm the signature
            $expected_signature = hash_hmac('sha256', $payload, $this->facebook_config['app_secret'], $raw = true);
            $isValid = $signature === $expected_signature;

            if (!$isValid) {
                error_log('Bad Signed JSON signature!');
                return null;
            }

            return $isValid;
        } catch (Exception $ex) {
            return false;
        }
    }

    private function _getEncodedData($signedRequest) {
        try {
            $encodedData = json_decode(base64_url_decode(explode('.', $signedRequest)[1]), true);
            /*
            $encodedData = {
                "status":true,
                "data":{
                    "algorithm":"HMAC-SHA256",
                    "issued_at":1552157956,
                    "player_id":"2522121991191915",
                    "request_payload":"2522121991191915_SOLO"
                }
            }
             */

            return $encodedData;
        } catch (Exception $ex) {
            return null;
        }
    }


function load_post() {

$signature = $this->post('signature');

            $isValid = $this->_validate($signature);

            if ($isValid) {
                $edata = $this->_getEncodedData($signature);
                $this->success($edata->data);
            } else {
                $this->error('Invalid Request!');
            }
..........................
}

Tuesday, March 5, 2019

how-to-integrate-facebook-instant-games tutorial Phaser

1.

https://www.emanueleferonato.com/2018/03/28/how-to-integrate-facebook-instant-games-api-in-your-phaser-game/


2.

https://www.emanueleferonato.com/2018/04/05/how-to-integrate-facebook-instant-games-api-in-your-phaser-game-displaying-player-name-and-profile-picture/


3.
https://www.facebook.com/embed/instantgames/<app_id>/player?game_url=https://localhost/mygame