Skip to main content
Glama
formatters.ts10.5 kB
import { Note, FormattedNote, Comment, FormattedComment, Like, FormattedLike } from "../types/note-types.js"; import { NoteUser, FormattedUser } from "../types/user-types.js"; import { Magazine, FormattedMagazine } from "../types/api-types.js"; import { FormattedMembershipNote, MembershipSummary, MembershipPlan } from "../types/membership-types.js"; // 記事データのフォーマット export function formatNote( note: any, username?: string, includeUserDetails?: boolean, analyzeContent?: boolean ): FormattedNote { const user = note.user || {}; // コンテンツ分析用データの整形 const hasEyecatch = Boolean(note.eyecatch || note.sp_eyecatch); const imageCount = note.image_count || (note.pictures ? note.pictures.length : 0); const price = note.price || 0; const isPaid = price > 0; return { id: note.id || "", key: note.key || "", title: note.name || "", excerpt: note.body ? (note.body.length > 100 ? note.body.substring(0, 100) + '...' : note.body) : '本文なし', publishedAt: note.publishAt || note.publish_at || '日付不明', likesCount: note.likeCount || note.like_count || 0, commentsCount: note.commentsCount || note.comment_count || 0, user: username || user.nickname || "", url: `https://note.com/${username || user.urlname || 'unknown'}/n/${note.key || ''}`, status: note.status || "", isDraft: note.status === "draft", format: note.format || "", editUrl: `https://note.com/${username || user.urlname || 'unknown'}/n/${note.key || ''}/edit`, hasDraftContent: Boolean(note.noteDraft), lastUpdated: note.noteDraft?.updatedAt || note.createdAt || "", // コンテンツ分析情報(オプション) contentAnalysis: analyzeContent ? { hasEyecatch, eyecatchUrl: note.eyecatch || note.sp_eyecatch || null, imageCount, hasVideo: note.type === "MovieNote" || Boolean(note.external_url), externalUrl: note.external_url || null, excerpt: note.body ? (note.body.length > 150 ? note.body.substring(0, 150) + '...' : note.body) : '', hasAudio: Boolean(note.audio), format: note.format || "unknown", highlightText: note.highlight || null } : undefined, // 価格情報 price, isPaid, priceInfo: note.price_info || { is_free: price === 0, has_multiple: false, has_subscription: false, oneshot_lowest_price: price }, // 設定情報 settings: { isLimited: note.is_limited || false, isTrial: note.is_trial || false, disableComment: note.disable_comment || false, isRefund: note.is_refund || false, isMembershipConnected: note.is_membership_connected || false, hasAvailableCirclePlans: note.has_available_circle_plans || false }, // 著者情報(詳細オプション) author: { id: user.id || "", name: user.name || user.nickname || "", urlname: user.urlname || "", profileImageUrl: user.user_profile_image_path || "", details: includeUserDetails ? { followerCount: user.follower_count || 0, followingCount: user.following_count || 0, noteCount: user.note_count || 0, profile: user.profile || "", twitterConnected: Boolean(user.twitter_nickname), twitterNickname: user.twitter_nickname || null, isOfficial: user.is_official || false, hasCustomDomain: Boolean(user.custom_domain), hasLikeAppeal: Boolean(user.like_appeal_text || user.like_appeal_image), hasFollowAppeal: Boolean(user.follow_appeal_text) } : null } }; } // ユーザーデータのフォーマット export function formatUser(user: NoteUser): FormattedUser { return { id: user.id || "", nickname: user.nickname || "", urlname: user.urlname || "", bio: user.profile?.bio || user.bio || '', followersCount: user.followerCount || user.followersCount || 0, followingCount: user.followingCount || 0, notesCount: user.noteCount || user.notesCount || 0, magazinesCount: user.magazineCount || user.magazinesCount || 0, url: `https://note.com/${user.urlname || ''}`, profileImageUrl: user.profileImageUrl || '' }; } // コメントデータのフォーマット export function formatComment(comment: Comment): FormattedComment { return { id: comment.id || "", body: comment.body || "", user: comment.user?.nickname || "匿名ユーザー", publishedAt: comment.publishAt || "" }; } // いいねデータのフォーマット export function formatLike(like: Like): FormattedLike { return { id: like.id || "", user: like.user?.nickname || "匿名ユーザー", createdAt: like.createdAt || "" }; } // マガジンデータのフォーマット export function formatMagazine(magazine: Magazine): FormattedMagazine { return { id: magazine.id || "", name: magazine.name || "", description: magazine.description || "", notesCount: magazine.notesCount || 0, publishedAt: magazine.publishAt || "", user: magazine.user?.nickname || "匿名ユーザー", url: `https://note.com/${magazine.user?.urlname || ''}/m/${magazine.key || ''}` }; } // メンバーシップ記事のフォーマット export function formatMembershipNote(note: any): FormattedMembershipNote { return { id: note.id || "", title: note.name || note.title || "", excerpt: note.body ? (note.body.length > 100 ? note.body.substring(0, 100) + '...' : note.body) : '本文なし', publishedAt: note.publishAt || note.published_at || note.createdAt || note.created_at || '日付不明', likesCount: note.likeCount || note.likes_count || 0, commentsCount: note.commentsCount || note.comments_count || 0, user: note.user?.nickname || note.creator?.nickname || "", url: note.url || (note.user ? `https://note.com/${note.user.urlname}/n/${note.key || ''}` : ''), isMembersOnly: note.is_members_only || note.isMembersOnly || true }; } // メンバーシップサマリーのフォーマット export function formatMembershipSummary(summary: any): MembershipSummary { const circle = summary.circle || {}; const owner = circle.owner || {}; const plans = summary.circlePlans || []; const planNames = plans.map((plan: any) => plan.name || "").filter((name: string) => name); return { id: circle.id || summary.id || "", key: circle.key || summary.key || "", name: circle.name || summary.name || "", urlname: circle.urlname || owner.urlname || "", price: circle.price || summary.price || 0, description: circle.description || "", headerImagePath: summary.headerImagePath || circle.headerImagePath || "", creator: { id: owner.id || "", nickname: owner.nickname || "", urlname: owner.urlname || "", profileImageUrl: owner.userProfileImagePath || "" }, plans: planNames, joinedAt: circle.joinedAt || "" }; } // メンバーシッププランのフォーマット export function formatMembershipPlan(plan: any): MembershipPlan { const circle = plan.circle || {}; const circlePlans = plan.circlePlans || []; const owner = circle.owner || {}; return { id: circle.id || plan.id || "", key: circle.key || plan.key || "", name: circlePlans.length > 0 ? circlePlans[0].name || "" : circle.name || plan.name || "", description: circle.description || plan.description || "", price: plan.price || circle.price || 0, memberCount: circle.subscriptionCount || circle.membershipNumber || 0, notesCount: plan.notesCount || 0, status: circle.isCirclePublished ? "active" : "inactive", ownerName: owner.nickname || owner.name || "", headerImagePath: plan.headerImagePath || circle.headerImagePath || "", plans: circlePlans.map((p: any) => p.name || "").filter((n: string) => n), url: owner.customDomain ? `https://${owner.customDomain.host}/membership` : `https://note.com/${owner.urlname || ""}/membership` }; } // 分析用データの集計 export function analyzeNotes(formattedNotes: FormattedNote[], query: string, sort: string) { return { totalFound: formattedNotes.length, analyzed: formattedNotes.length, query, sort, // エンゲージメント分析 engagementAnalysis: { averageLikes: formattedNotes.reduce((sum, note) => sum + note.likesCount, 0) / formattedNotes.length || 0, averageComments: formattedNotes.reduce((sum, note) => sum + (note.commentsCount || 0), 0) / formattedNotes.length || 0, maxLikes: Math.max(...formattedNotes.map(note => note.likesCount)), maxComments: Math.max(...formattedNotes.map(note => note.commentsCount || 0)) }, // コンテンツタイプ分析 contentTypeAnalysis: { withEyecatch: formattedNotes.filter(note => note.contentAnalysis?.hasEyecatch).length, withVideo: formattedNotes.filter(note => note.contentAnalysis?.hasVideo).length, withAudio: formattedNotes.filter(note => note.contentAnalysis?.hasAudio).length, averageImageCount: formattedNotes.reduce((sum, note) => sum + (note.contentAnalysis?.imageCount || 0), 0) / formattedNotes.length || 0 }, // 価格分析 priceAnalysis: { free: formattedNotes.filter(note => !note.isPaid).length, paid: formattedNotes.filter(note => note.isPaid).length, averagePrice: formattedNotes.filter(note => note.isPaid).reduce((sum, note) => sum + (note.price || 0), 0) / formattedNotes.filter(note => note.isPaid).length || 0, maxPrice: Math.max(...formattedNotes.map(note => note.price || 0)), minPrice: Math.min(...formattedNotes.filter(note => note.isPaid).map(note => note.price || 0)) || 0 }, // 著者分析 authorAnalysis: { uniqueAuthors: [...new Set(formattedNotes.map(note => note.author?.id))].length, averageFollowers: formattedNotes.reduce((sum, note) => sum + (note.author?.details?.followerCount || 0), 0) / formattedNotes.length || 0, maxFollowers: Math.max(...formattedNotes.map(note => note.author?.details?.followerCount || 0)), officialAccounts: formattedNotes.filter(note => note.author?.details?.isOfficial).length, withTwitterConnection: formattedNotes.filter(note => note.author?.details?.twitterConnected).length, withCustomEngagement: formattedNotes.filter(note => note.author?.details?.hasLikeAppeal || note.author?.details?.hasFollowAppeal).length } }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shimayuz/note-com-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server