Closed67

漫画を一括アップロードするやつを作る

ピン留めされたアイテム
椎名椎名

フロントエンド始めた頃にれとるときゃりーさんが公開していた Vue.js + Firebaseのプロジェクトを見て、色々真似してサービスを作ったのが一番最初だったので、誰かの参考になればな~と思って経過を書きながら作りました。

コードは汚いですが、GitHubにソースはすべて載せているので何かしらの参考になれば嬉しいです

https://github.com/s7tya/samari-page

Hidden comment
Hidden comment
Hidden comment
Hidden comment
椎名椎名

ChakraUIでLP実装done!w
斜めのやつを ::before clip-path でいじってたら時間かかった CSS 下手

椎名椎名

next-seo でSEO/OG Imageとか設定する
忘れてたせいで↓のfirebaseのやつとコンフリクトしてしんどい

椎名椎名

firebaseやるぞやるぞやるぞ
公開しても大丈夫だけど環境切り替えたりしたいので .env から取る

Image from Gyazo

Hidden comment
椎名椎名

ドメイン追加するために先に空Project作って後からgh繋げてたのでVercelがNext.jsとして認識してなかったらしく、デプロイ成功するけどトップページ404になってた かなしい

Hidden comment
椎名椎名

Drag & Dropを作る

椎名椎名

公式サイトにpreviewの生成書いてあったのでそのままやったらできた

椎名椎名

Node.jsの File に { preview: string } を入れたやつの型がほしかったので

interface FileWithPreview extends {
  preview: string
}

を作った

椎名椎名

VSCodeに (File & { preview: string}) って表示されてびっくりしたけど同じ扱いだった

椎名椎名

今は画像アップロードすると置き換えられてしまうから追加方式に変えなきゃな、リセットも追加したい

椎名椎名

画像のアップロードが置き換えになっているので追加していく方式にする

椎名椎名

型怒られたので setState の型をちゃんとしようと思ったけど

setImages: Dispatch<SetStateAction<FileWithPreview[]>>; みたいになる(???)

椎名椎名

ツイートするようにする

椎名椎名
import type { NextApiRequest, NextApiResponse } from "next";
import { TwitterClient } from "twitter-api-client";

export default async (req: NextApiRequest, res: NextApiResponse) => {
  // Post以外弾く
  if (req.method !== "POST") {
    return res.status(400);
  }

  const body = JSON.parse(req.body);

  if (!process.env.TWITTER_API_KEY || !process.env.TWITTER_API_SECRET) {
    return Error("No API key or secret.");
  }

  const twitterClient = new TwitterClient({
    apiKey: process.env.TWITTER_API_KEY,
    apiSecret: process.env.TWITTER_API_SECRET,
    accessToken: body.accessToken,
    accessTokenSecret: body.accessTokenSecret,
  });

  await twitterClient.tweets.statusesUpdate({
    status: `<3`,
  });

  res.status(200).end();
};
椎名椎名

とりあえずAPI routesからツイートするやつを作った、アクセストークンとか触るの普通に怖いし嫌だなあ

椎名椎名

そもそもAccessTokenあってもどうやってツイートするんだろう

椎名椎名

API routesでできるかなと思って試してるけど、ツイート自体はできても media/upload のAPIが複雑だしエラー出て動かない、なにがだめなの............... ; ;

椎名椎名

あ~~~API叩いてるのだけ切り出して動かしてたらどうやらバイト数がおかしいらしい、どうやって取得すればいいんですか???

椎名椎名
import { TwitterClient } from "twitter-api-client";
import { readFileSync } from "fs";
import { Buffer } from "buffer";

const twitterClient = new TwitterClient({
  apiKey: "API KEY",
  apiSecret: "API SECRET",
  accessToken: "ACCESS TOKEN",
  accessTokenSecret: "ACCESS TOKEN SECRET",
});

const image = readFileSync("./image.png").toString("base64");
const size = Buffer.byteLength(image, "base64");

// Init
const init = await twitterClient.media.mediaUploadInit({
  command: "INIT",
  media_type: "image/png",
  total_bytes: size,
});

// Append
await twitterClient.media.mediaUploadAppend({
  command: "APPEND",
  media_id: init.media_id_string,
  media_data: image,
  segment_index: "0",
});

// Finalize
await twitterClient.media.mediaUploadFinalize({
  command: "FINALIZE",
  media_id: init.media_id_string,
});

await twitterClient.tweets.statusesUpdate({
  status: `<3`,
  media_ids: init.media_id_string,
});

椎名椎名

Buffer.byteLength() にもエンコードの指定があることに気づかなかったり、そもそも readFileSync(path, { encoding: "base64" }) とかして .png をbase64として読み込むとかいうわけわからんことをしてた

椎名椎名

あとは、 Data URLは純粋な base64に変換したものに色々付け加えられているので .replace() とか挟んで除去しないと行けなかった(当たり前)

椎名椎名
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function isAuthenticated() {
        return request.auth != null;
    }

    function isUserAuthenticated(userId) {
        return isAuthenticated() && userId == request.auth.uid;
    }
        
      match /tokens/{userId} {
        allow get, create, update: if isUserAuthenticated(userId);
    }
  
    match /{document=**} {
      allow read, write: if false;
    }
  }
}
このスクラップは2021/12/31にクローズされました