1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

誰も投稿できないSNSを作ってみた

Posted at

初めに

AI(ChatGPT)に「SNSのコード書きたいからアイデア出して」って言ったら

誰も投稿できないSNS:
誰も投稿、リアクションができないけど、毎日誰かが投稿してるSNS

的なことが返ってきたので作ってみました

仕組み

投稿にはAIを用いて、自動的かつ15~45分程度の間隔で投稿させる仕組みで書こうと思います。(投稿と返信しかできません)

使用したもの

AIモデルなど

使用したNode.jsのモジュール:

  • express: 鯖立てに使いました
  • https: httpsで鯖を立てるときに使いました
  • cors: ほかのPCとかからアクセスできるようにするために使いました
  • ip: ipv4アドレスの取得に使いました
  • OpenAI: LM studioがOpenAI互換なのでこれを使いました
  • fs: プロンプト、投稿内容の取得とかに使用しました
  • readline: JSONLファイルをロード、書き込みするときに使いました
  • path: fs.readiFileSync()のときのパス指定などに使いました
  • uuid: 投稿や返信のIDの生成に使用しました

ファイルの配置

今回は以下のようにファイルを作りました(ファイルの分け方わからなくなってファイル数多いです。)

(この上にbackendsというフォルダがあります)
|
│  app.js
│  settings.js
│
├─cert
│      localhost-key.pem
│      localhost.pem
│
├─public
│  ├─home
│  │      index.html
│  │      script.js
│  │      style.css
│  │
│  └─search
│          filter.js
│          search.html
│          style.css
│
└─src
    ├─codes
    │      filter.js
    │      genres.js
    │      logfile.js
    │      manageaction.js
    │      managehash.js
    │      post.js
    │      prompt.js
    │      savePost.js
    │      scheduler.js
    │      time.js
    │
    └─data
        │  posts.jsonl
        │
        ├─log
        │      app.log
        │
        ├─prompts
        │  │  user1.json
        │  │  user2.json
        │  │  user3.json
        │  │
        │  └─system
        │          managehash.json
        │
        └─temp

コード(一部)

ファイル全部載せたら多分長くなるので、app.js, genres.js, scheduler.jsだけ載せます。

app.js:

app.js
//import modules
const express = require('express');
const cors = require('cors');
const path = require('path');
const https = require('https');
const fs = require('fs');
const ip = require('ip');
const { checkexistprompts } = require('./src/codes/prompt');
const { startAutopost, startAutoreply } = require('./src/codes/scheduler');
const { filter } = require('./src/codes/filter')
const settings = require('./settings');
const log = require('./src/codes/logfile');

//server settings
const app = express();
app.use(cors());
app.use(express.json());
app.use(
    settings.server_settings.isdevelop || settings.server_settings.debug
        ?express.static(path.join(__dirname, 'public'), {index: false})
        :express.static(path.join(__dirname, 'public'))
);

//endpoints
app.get('/', (req, res) => {
    console.log('[debug]: access detected (endpoint: /)');
    res.sendFile(path.join(__dirname, 'public', 'home', 'index.html'));
})

app.get('/search', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'search', 'search.html'))
})

app.get('/get', (req, res) => {
    try {
        const text = fs.readFileSync(path.join(__dirname, 'src', 'data', 'posts.jsonl'), 'utf-8');
        const lines = text.trim().split('\n');
        const posts = lines.map(line => JSON.parse(line)); // JSONLを配列化
        res.json({ content: posts }); // ここで配列として返す
    } catch (err) {
        res.json({ error: err.message });
    }
});

app.post('/filter', async (req, res) => {
    const method = req.body.searchmethod;
    const input = req.body.input;
    
    const result = await filter(input, method);
 
    res.json({data: result});
})

settings.server_settings.log
    ?console.info('[info]: logmode enabled.')
    :console.info('[noinfo]: logmode is disabled.')

settings.server_settings.isdevelop || settings.server_settings.debug
    ? (console.warn('warning: developer mode enabled.'), console.log(`[debug]: llm endpoint: ${settings.llm_settings.apiendpoint}`))
    : console.log('developer mode is disabled')

console.log('checking required promptfiles...');

console.log('checking ManageActionsfile existing...')

if(!fs.existsSync(settings.dir_settings.actionmanagefile)) {
    console.error(`[err]: ManageAction file is not exist \n filepath: "${settings.dir_settings.actionmanagefile}.\n process exited, code:1`)
    process.exit(1);
}

(async () => {
   let check = await checkexistprompts(settings.dir_settings.requiredprompts);
   if(check !== true) {
    console.error('server closed. error code: 404 (file not found)');
    process.exit(1)
   }
})();

console.log('starting autoPost..');
startAutopost();

console.log(`starting autoreply..`);
startAutoreply(true, 1)

if(settings.server_settings.https) {
    const options = {
        key: fs.readFileSync(path.join(__dirname, 'cert', 'localhost-key.pem')),
        cert: fs.readFileSync(path.join(__dirname, 'cert', 'localhost.pem'))
}
    settings.server_settings.isipv4
        ? https.createServer(options, app).listen(settings.server_settings.port, ip.address(), () => {console.log(`server running on https://${ip.address()}:${settings.server_settings.port}`), log(`server running on https://${ip.address()}:${settings.server_settings.port}`)})
        : https.createServer(options, app).listen(settings.server_settings.port, () => {console.log(`server running on port https://localhost:${settings.server_settings.port}`), log(`server running on https://localhost:${settings.server_settings.port}`)})
} else {
    console.warn('[warn]: https disabled by settings.')
    log('Warning: https disabled by settings.')
    settings.server_settings.isipv4
        ? app.listen(settings.server_settings.port, ip.address(), () => { console.log(`server running on http://${ip.address()}:${settings.server_settings.port}`), log(`server running on http://${ip.address()}:${settings.server_settings.port}`) })
        : app.listen(settings.server_settings.port, () => { console.log(`server running on port http://localhost:${settings.server_settings.port}`), log(`server running on http://localhost:${settings.server_settings.port}`)})
    }

genres.js

genres.js
const setting = require('../../settings');
const { OpenAI } = require('openai');
const { getprompt, getrandomprompt } = require('./prompt')
const { llm_settings } = require('../../settings')

const openai = new OpenAI({
  apiKey: llm_settings.apikey,
  baseURL: llm_settings.apiendpoint
});


//get llm response(第二引数はない場合はnullで埋めてください。)
async function genres (promptname, prompt, random, action) {

    let model = llm_settings.model;
    //モデル名が指定されてない(null, undefinedなど)場合はmodelsで取得し先頭([0])のidをモデル名として設定
    if(!model) {
        await fetch(`${llm_settings.apiendpoint}/models`, {
            method: "GET"
        })
        .then(response => response.json())
        .then(data => {
            model = data.data[0].id
        })
    }

    let promptdata; 
    random
   ? promptdata = await getrandomprompt()
   : ( action 
        ? promptdata = await getprompt(promptname, prompt, true) 
        : promptdata = await getprompt(promptname, prompt, false)
    )
    if (!promptdata) {
        return {Error: "error: The prompt data was null."};
    }

    if(setting.server_settings.isdevelop || setting.server_settings.debug) {
        if(setting.server_settings.isdevelop) {
            console.debug(`[debug]: modelname: ${model}`)
        }

        console.log(`[debug]: promptname: ${promptdata.name}`)
        console.log(`[debug]: systemprompt: ${promptdata.sysprompt}`);
        console.log(`[debug]: prompt: ${promptdata.prompt}`);
    }
 
    const res = await openai.chat.completions.create({
        model: model,
        messages: [
            {role: "system", content: promptdata.sysprompt},
            {role: "user", content: promptdata.prompt}
        ]
    });

    return {name: promptdata.name, avatar: promptdata.avatar || promptdata.name[0] , res: JSON.stringify(res.choices[0].message.content), uid: promptdata.uid}
    
}

/*
(async () => {
   await console.log(await genres('prompt', null))
})();
*/

module.exports = {
    genres,
}

scheduler.js

scheduler.js
const { post_settings, server_settings } = require('../../settings');
const { post } = require('./post');
const { canreply } = require('./manageaction');
const { getinterval } = require('./time');
const { getrandomprompt, getpromptnames } = require('./prompt');
const log = require('./logfile');

let alreadyrunning_post = false;
let alreadyrunning_reply = false;

async function RandomTimeReply() {
    const promptname = await getpromptnames()
    await canreply(promptname[Math.floor(Math.random() * promptname.length)]);

    const interval = getinterval(false, true);

    if(server_settings.log || server_settings.debug || server_settings.isdevelop) {
        if(server_settings.isdevelop || server_settings.debug) {
            console.info(`[debug]: next reply is ${interval.tominutes} minutes(${interval.formated} ms) later.`);
        }

        log(`[log]: next reply is ${interval.tominutes} minutes(${interval.formated} ms) later.`)
    }

    setTimeout(() => {
        RandomTimeReply()
    }, interval.formated);
}

async function RandomTimePost (name) {
    await post("", null, true);
    const interval = getinterval(true, false);
    
    if(server_settings.log || server_settings.debug || server_settings.isdevelop) {
        if(server_settings.isdevelop || server_settings.debug) {
            console.info(`[debug]: next post is ${interval.tominutes} minutes(${interval.formated} ms) later.`);
        }

        log(`[log]: next post is ${interval.tominutes} minutes(${interval.formated} ms) later.`)
    }

    setTimeout(() => {
     RandomTimePost()       
    }, interval.formated);
}

function startAutopost() {
    if (alreadyrunning_post === true) {
        return {error: "Autopost is already running."}
    }

    alreadyrunning_post = true;
    RandomTimePost()
}

function startAutoreply(timeout, minutes) {
    if(alreadyrunning_reply) {
        return {error: "Autoreply is already running."};
    }

    alreadyrunning_reply = true;
        timeout
        ? (console.log(`next reply is ${minutes} (${minutes * 1000 * 10 *6} ms) later.`),
            setTimeout(() => {
            RandomTimeReply();
        }, minutes * 1000 * 10 * 6))
        : RandomTimeReply()
    }

module.exports = {
    startAutopost,
    startAutoreply
}

その他のファイルはGithubにあげてます:
https://github.com/qqn5192/can-t-Post-SNS

仕組み

genresにプロンプトの名前を渡して、プロンプトを取得した後にopenai.chat.completions.create()で投稿、返信を生成しています。(投稿の保存にはJSONLファイルを使用しました。)

動かしてるときの画像

ホーム:

動作確認.png

検索:

動作確認2.png

サーバー側:

動作確認3.png

最後に

今回は誰も投稿できないSNSを作ってみました。今回もreadlineopenaiなどのモジュールの使い方の説明やコードの修正をChatGPTにやってもらいました。
それではまたお会いしましょう!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?