Skip to content

Commit

Permalink
add post to twitter
Browse files Browse the repository at this point in the history
  • Loading branch information
amay077 committed May 5, 2024
1 parent 7c79591 commit f19f069
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 10 deletions.
6 changes: 4 additions & 2 deletions src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
let accessToken = localStorage.getItem('sci_accessToken');
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
console.log(`FIXME h_oku 後で消す -> state:`, state);
if (accessToken != null) {
} else if (code) {
} else if (state == null && code) {
(async () => {
const res = await fetch(`${Config.API_ENDPOINT}/token?code=${code}`)
const jsonData = await res.json();
Expand Down Expand Up @@ -49,7 +51,7 @@
{#if accessToken == null}
{#if code == null}
<Auth />
{:else}
{:else if state == null}
<span>Connecting...</span>
{/if}

Expand Down
12 changes: 10 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const config_dev = {
CLIENT_ID: 'JXKR3OYSVKYPR4YSIYFUXUASGG2SXDIR1I2XDLIWIAMUK2ES',
REDIRECT_URI: 'http://localhost:8080/#/auth',
API_ENDPOINT: `http://localhost:9999/.netlify/functions`,
API_ENDPOINT: `http://localhost:63406/.netlify/functions`,

post_targets: {
mastodon: {
Expand All @@ -17,7 +17,11 @@ const config_dev = {
server: 'mstdn.jp',
MASTODON_CLIENT_ID: 'gIAagB7-8KP6XEW1xHW3Wh3UjOH9A-ircwMlZX-80xw',
MASTODON_CLIENT_SECRET: 'FlJm2oIaLBEJi4uWGk51ke_VPiwYB5lM5vCB5J5Cf9E',
}
},
},
twitter: {
client_id: 'cFdITnRJb2t6Z1FYTktjUmU0WFg6MTpjaQ',
redirect_uri: 'http%3A%2F%2Flocalhost%3A8080',
}
}
}
Expand All @@ -41,6 +45,10 @@ const config_prod = {
MASTODON_CLIENT_ID: 'gIAagB7-8KP6XEW1xHW3Wh3UjOH9A-ircwMlZX-80xw',
MASTODON_CLIENT_SECRET: 'FlJm2oIaLBEJi4uWGk51ke_VPiwYB5lM5vCB5J5Cf9E',
}
},
twitter: {
client_id: 'cFdITnRJb2t6Z1FYTktjUmU0WFg6MTpjaQ',
redirect_uri: 'https%3A%2F%2Famay077.github.io%2Fswarm-check-ins',
}
}}

Expand Down
88 changes: 84 additions & 4 deletions src/lib/MainContent.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@
import MastodonConnection from "./MastodonConnection.svelte";
import { BskyAgent, RichText } from "@atproto/api";
import BlueSkyConnection from "./BlueSkyConnection.svelte";
import { loadPostSetting, savePostSetting, type SettingDataBluesky, type SettingDataMastodon, type SettingType } from "./func";
import { loadPostSetting, savePostSetting, type SettingDataBluesky, type SettingDataMastodon, type SettingDataTwitter, type SettingType } from "./func";
import TwitterConnection from "./TwitterConnection.svelte";
import { Config } from "../config";
// localstorage からアクセストークンを取得する
const accessToken = localStorage.getItem('sci_accessToken');
const postSettings: {
mastodon: SettingDataMastodon | null,
bluesky: SettingDataBluesky | null,
twitter: SettingDataTwitter | null,
} = {
mastodon: loadPostSetting('mastodon'),
bluesky: loadPostSetting('bluesky'),
twitter: loadPostSetting('twitter'),
};
const postTo: { [K in SettingType]: boolean } = {
mastodon: postSettings?.mastodon?.enabled ?? false,
bluesky: postSettings?.bluesky?.enabled ?? false,
twitter: postSettings?.twitter?.enabled ?? false,
};
const title = window.navigator?.canShare != null ? `Share...` : 'Copy to clipboard';
Expand All @@ -31,6 +36,34 @@
console.log(`onMount`);
try {
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
if (params.get('state') == 'twitter_callback' && params.has('code')) {
const code = params.get('code') ?? '';
const res = await fetch(`${Config.API_ENDPOINT}/twitter_token?code=${code}`)
if (res.ok) {
const data = await res.json();
postSettings.twitter = { type: 'twitter', title: 'Twitter', enabled: true, access_token_response: data };
savePostSetting(postSettings.twitter);
postTo.twitter = true;
alert('Twitter に接続しました。');
} else {
console.error(`twitter 接続エラー -> res:`, res);
alert('Twitter に接続できませんでした。');
}
const url = new URL(window.location.href);
params.delete('code');
params.delete('state');
url.hash = '';
url.search = params.toString();
history.replaceState(null, '', url.toString());
}
// アクセストークンを使用してチェックイン一覧を取得する
const response = await fetch(`https://api.foursquare.com/v2/users/self/checkins?oauth_token=${accessToken}&v=20230823&limit=100`);
Expand Down Expand Up @@ -105,11 +138,16 @@
errors.push('Mastodon');
}
break;
case 'bluesky':
case 'bluesky':
if (!(await postToBlueSky(text))) {
errors.push('BlueSky');
}
break;
case 'twitter':
if (!(await postToTwritter(text))) {
errors.push('Twitter');
}
break;
}
}
Expand All @@ -125,7 +163,7 @@
}
} else {
alert(`${errors.join(', ')}}に投稿できませんでした。`);
alert(`${errors.join(', ')}に投稿できませんでした。`);
}
posting = false;
Expand Down Expand Up @@ -190,9 +228,38 @@
}
};
const postToTwritter = async (text: string): Promise<boolean> => {
try {
const settings = postSettings.twitter!;
const ACCESS_TOKEN = settings.access_token_response.access_token;
const res = await fetch(`${Config.API_ENDPOINT}/twitter_post`, {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
body: JSON.stringify({ access_token: ACCESS_TOKEN, text }),
});
if (res.ok) {
} else {
return false;
}
return true;
} catch (error) {
console.error(`postToMastodon -> error:`, error);
return false;
}
};
const onChangePostSettings = () => {
postSettings.mastodon = loadPostSetting('mastodon');
postSettings.bluesky = loadPostSetting('bluesky');
postSettings.twitter = loadPostSetting('twitter');
Object.entries(postTo).forEach(([k, v]) => {
postTo[k as SettingType] = postSettings?.[k as SettingType]?.enabled ?? false;
});
};
</script>

Expand All @@ -204,14 +271,22 @@
<div class="d-flex flex-column gap-2">
<div class="form-check mb-0 d-flex flex-row align-items-start gap-1">
<input class="mt-1 form-check-input" type="checkbox" bind:checked={postTo.mastodon} id="mastodon" disabled={postSettings.mastodon == null}>
<MastodonConnection on:onChange={onChangePostSettings} />
<div class="w-100">
<MastodonConnection on:onChange={onChangePostSettings} />
</div>
</div>
<div class="form-check mb-0 d-flex flex-row align-items-start gap-1">
<input class="mt-1 form-check-input" type="checkbox" bind:checked={postTo.bluesky} id="bluesky" disabled={postSettings.bluesky == null}>
<div class="w-100">
<BlueSkyConnection on:onChange={onChangePostSettings} />
</div>
</div>
<div class="form-check mb-0 d-flex flex-row align-items-start gap-1">
<input class="mt-1 form-check-input" type="checkbox" bind:checked={postTo.twitter} id="twitter" disabled={postSettings.twitter == null}>
<div class="w-100">
<TwitterConnection on:onChange={onChangePostSettings} />
</div>
</div>
</div>


Expand Down Expand Up @@ -245,6 +320,11 @@
{#if postSettings.bluesky != null && postTo.bluesky}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -3.268 64 68.414" width="16" height="16"><path fill="currentColor" d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805zm36.254 0C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745z"/></svg>
{/if}
{#if postSettings.twitter != null && postTo.twitter}
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitter" viewBox="0 0 16 16">
<path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"/>
</svg>
{/if}

<span>Post</span>
</div>
Expand Down
68 changes: 68 additions & 0 deletions src/lib/TwitterConnection.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { Config } from "../config";
import { deletePostSetting, loadPostSetting } from "./func";
const dispatch = createEventDispatcher<{ onChange: void }>();
let expanded = false;
let postSettings = loadPostSetting('twitter');
const onConnectToTwitter = () => {
// const url = `https://twitter.com/i/oauth2/authorize?response_type=code&client_id=cFdITnRJb2t6Z1FYTktjUmU0WFg6MTpjaQ&redirect_uri=http%3A%2F%2Flocalhost%3A8080&scope=tweet.read%20tweet.write%20users.read%20offline.access&state=twitter_callback&code_challenge=challenge&code_challenge_method=plain`;
const url = `https://twitter.com/i/oauth2/authorize?response_type=code&client_id=${Config.post_targets.twitter.client_id}&redirect_uri=${Config.post_targets.twitter.redirect_uri}&scope=tweet.read%20tweet.write%20users.read&state=twitter_callback&code_challenge=challenge&code_challenge_method=plain`;
// const url = `https://${settings.server}/oauth/authorize?client_id=${settings.MASTODON_CLIENT_ID}&response_type=code&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=write`;
// url をこのタブで開く
window.open(url, '_self');
};
</script>

<div>

<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="d-flex flex-row gap-1 align-items-center" style="cursor: pointer;" on:click={() => {
expanded = !expanded;
}}>
<h5 class="mb-0">Twitter</h5>
{#if !expanded}
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-chevron-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/>
</svg>
{:else}
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" class="bi bi-chevron-down" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"/>
</svg>
{/if}
</div>
{#if expanded}
<div class="p-1">

{#if postSettings != null}
<div class="d-flex flex-row gap-2 align-items-center">
<span>接続済み</span>
<button class="btn btn-sm btn-outline-primary" style="width: 60px;" on:click={() => {
postSettings = null;
deletePostSetting('twitter');
dispatch('onChange');
}}>切断</button>
</div>
{:else}
<div class="d-flex flex-column gap-1">
<div class="d-flex flex-column gap-1">
<div class="d-flex flex-row gap-1">
<button class="btn btn-sm btn-primary" style="width: 60px;" on:click={onConnectToTwitter}>接続</button>
</div>
</div>
</div>
{/if}


</div>
{/if}
</div>

21 changes: 19 additions & 2 deletions src/lib/func.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,28 @@ export type SettingDataBluesky = {
password: string,
}
};
export type SettingData = SettingDataMastodon | SettingDataBluesky;

export type SettingDataTwitter = {
type: 'twitter',
title: 'Twitter',
enabled: boolean,
access_token_response: {
token_type: string,
expires_in: number,
access_token: string,
refresh_token: string,
scope: string,
}
};

export type SettingData = SettingDataMastodon | SettingDataBluesky | SettingDataTwitter;

export type SettingType = SettingData['type'];

export type SettingDataType<T extends SettingType> = T extends 'mastodon' ? SettingDataMastodon : SettingDataBluesky;
export type SettingDataType<T extends SettingType> =
T extends 'mastodon' ? SettingDataMastodon :
T extends 'bluesky' ? SettingDataBluesky :
SettingDataTwitter;

export function savePostSetting(data: SettingData) {
localStorage.setItem(`sci_setting_${data.type}`, JSON.stringify(data));
Expand Down

0 comments on commit f19f069

Please sign in to comment.