初めに
AI(ChatGPT)に「SNSのコード書きたいからアイデア出して」って言ったら
誰も投稿できないSNS:
誰も投稿、リアクションができないけど、毎日誰かが投稿してるSNS
的なことが返ってきたので作ってみました
仕組み
投稿にはAIを用いて、自動的かつ15~45分程度の間隔で投稿させる仕組みで書こうと思います。(投稿と返信しかできません)
使用したもの
AIモデルなど
- AIモデル:今回はliquid/lfm2-1.2bを使用しました
- LM studio: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ファイルを使用しました。)
動かしてるときの画像
ホーム:
検索:
サーバー側:
最後に
今回は誰も投稿できないSNSを作ってみました。今回もreadline
、openai
などのモジュールの使い方の説明やコードの修正をChatGPTにやってもらいました。
それではまたお会いしましょう!