LoginSignup
0
0

More than 1 year has passed since last update.

TwitterとYoutubeのURLから投稿idを取得する関数とそのテスト

Last updated at Posted at 2023-03-03

ちょっと必要だったので便利関数を作った。
要件的に簡単な処理であったし、丁度良かったので初めてJestを使ってみた。

chatGPTにある程度やらせてcopilot使いながら手直し、テスト書かせて手直しという感じ。
最近はコードを0から書くことが全く無くなったがまだまだ使いこなせていない気もする。

目次

Twitter関連の関数

utils/twitter.ts
export const isTwitterUrl = (url: string) => {
  const twitterHostnames = [
    'twitter.com',
    'www.twitter.com',
    'mobile.twitter.com',
    // 'pbs.twimg.com',
    // 't.co',
  ];

  try {
    const parser = new URL(url);
    return twitterHostnames.includes(parser.hostname);
  } catch (error) {
    return false;
  }
};

export const isTweetUrl = (url: string) => {
  if (!isTwitterUrl(url)) return false;
  const regex =
    /^https?:\/\/(?:www\.|mobile\.)?twitter\.com\/(?:#!\/)?([a-zA-Z0-9_]{1,15})\/status(?:es)?\/(\d+)(?:\?.*)?$/;
  return regex.test(url);
};

export const getTweetId = (url: string) => {
  if (!isTweetUrl(url)) return null;

  const regexes = [
    /https?:\/\/twitter\.com\/(\w+)\/status(es)?\/(\d+)/,
    /https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(es)?\/(\d+)/,
    /https?:\/\/mobile\.twitter\.com\/(\w+)\/status(es)?\/(\d+)/,
  ];

  for (const regex of regexes) {
    const match = url.match(regex);
    if (match) {
      return match[match.length - 1];
    }
  }

  return null;
};

Twitter関連の関数のテスト

utils/__tests__/twitter.test.ts
import { isTwitterUrl, isTweetUrl, getTweetId } from '../twitter';

describe('isTwitterUrl', () => {
  test('returns true for valid twitter URLs', () => {
    expect(isTwitterUrl('https://twitter.com/')).toBe(true);
    expect(isTwitterUrl('https://twitter.com/hashtag/tbt')).toBe(true);
    expect(isTwitterUrl('https://twitter.com/search?q=hello')).toBe(true);
    expect(isTwitterUrl('https://twitter.com/Twitter/status/1234567890')).toBe(true);
    expect(isTwitterUrl('https://www.twitter.com/Twitter/status/1234567890')).toBe(true);
    expect(isTwitterUrl('https://mobile.twitter.com/Twitter/status/1234567890')).toBe(true);
  });

  test('returns false for invalid twitter URLs', () => {
    expect(isTwitterUrl('https://pbs.twimg.com/profile_images/1234567890/photo.jpg')).toBe(false);
    expect(isTwitterUrl('https://t.co/abcdefg')).toBe(false);
    expect(isTwitterUrl('https://www.google.com/')).toBe(false);
  });
});

describe('isTweetUrl', () => {
  test('valid tweet url', () => {
    const urls = [
      'https://twitter.com/elonmusk/status/1367528057626767874',
      'https://www.twitter.com/elonmusk/status/1367528057626767874',
      'https://mobile.twitter.com/elonmusk/status/1367528057626767874',
      'https://twitter.com/elonmusk/statuses/1367528057626767874',
      'https://www.twitter.com/elonmusk/statuses/1367528057626767874',
      'https://mobile.twitter.com/elonmusk/statuses/1367528057626767874',
    ];
    urls.forEach((url) => {
      expect(isTweetUrl(url)).toBe(true);
    });
  });

  test('invalid tweet url', () => {
    const urls = [
      'https://twitter.com/',
      'https://twitter.com/elonmusk',
      'https://twitter.com/elonmusk/status/',
      'https://twitter.com/elonmusk/status/abc',
      'https://www.google.com/search?q=elon+musk+twitter&oq=elon+musk+twitter',
    ];
    urls.forEach((url) => {
      expect(isTweetUrl(url)).toBe(false);
    });
  });
});

describe('getTweetId', () => {
  it('returns null for invalid URL', () => {
    expect(getTweetId('not a URL')).toBeNull();
    expect(getTweetId('https://example.com')).toBeNull();
    expect(getTweetId('https://twitter.com/')).toBeNull();
    expect(getTweetId('https://twitter.com/user')).toBeNull();
    expect(getTweetId('https://twitter.com/user/status')).toBeNull();
    expect(getTweetId('https://twitter.com/user/status/invalidid')).toBeNull();
  });

  it('returns the tweet ID for valid URLs', () => {
    expect(getTweetId('https://twitter.com/user/status/1234567890')).toBe('1234567890');
    expect(getTweetId('https://twitter.com/user/statuses/1234567890')).toBe('1234567890');
    expect(getTweetId('https://twitter.com/user/status/1234567890?ref_src=twsrc%5Etfw')).toBe(
      '1234567890'
    );
    expect(getTweetId('https://mobile.twitter.com/user/status/1234567890')).toBe('1234567890');
  });
});

Youtube関連の関数

utils/youtube.ts
export const isYouTubeUrl = (url: string) => {
  try {
    const parser = new URL(url);
    return parser.hostname === 'www.youtube.com' || parser.hostname === 'youtu.be';
  } catch (error) {
    return false;
  }
};

export const isYouTubeVideoUrl = (url: string) => {
  if (!isYouTubeUrl(url)) return false;
  const parser = new URL(url);
  return (
    parser.pathname.startsWith('/watch') ||
    parser.pathname.startsWith('/embed') ||
    (parser.hostname === 'youtu.be' && !!parser.pathname.match(/^\/[0-9A-Za-z_-]{10,}$/))
  );
};

export const getVideoId = (url: string) => {
  if (!isYouTubeVideoUrl(url)) return null;
  const regex = /(?:\/|v=)([0-9A-Za-z_-]{10,})+(?:[%#?&]|$)/;
  const match = url.match(regex);
  return match ? match[1] : null;
};

export const getThumbnailUrl = (youtubeVideoId: string) => {
  return `https://img.youtube.com/vi/${youtubeVideoId}/maxresdefault.jpg`;
};

Youtube関連の関数のテスト

utils/__tests__/youtube.test.ts
import { isYouTubeUrl, isYouTubeVideoUrl, getVideoId } from '../youtube';

// 参考
// https://takashiski.hatenablog.com/entry/2021/09/19/124500

describe('isYouTubeUrl', () => {
  test('should return true for www.youtube.com', () => {
    expect(isYouTubeUrl('https://www.youtube.com/watch?v=dQw4w9WgXcQ')).toBe(true);
    expect(isYouTubeUrl('https://www.youtube.com/embed/dQw4w9WgXcQ')).toBe(true);
    expect(isYouTubeUrl('https://www.youtube.com/channel/UC-lHJZR3Gqxm24_Vd_AJ5Yw')).toBe(true);
  });
  test('should return true for youtu.be', () => {
    expect(isYouTubeUrl('https://youtu.be/dQw4w9WgXcQ')).toBe(true);
  });
  test('should return false for other URLs', () => {
    expect(isYouTubeUrl('https://www.google.com/')).toBe(false);
  });
});

describe('isYouTubeVideoUrl', () => {
  test('returns true for valid YouTube video URLs', () => {
    const validUrls = [
      'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
      'https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=10s',
      'https://www.youtube.com/embed/dQw4w9WgXcQ',
      'https://www.youtube.com/embed/dQw4w9WgXcQ?start=10',
      'https://youtu.be/dQw4w9WgXcQ',
      'https://www.youtube.com/watch?v=ngNhpdaT5V0&list=PLxai42gkPeMN1nTZFD4PEc96sMI8mdT03&index=3',
    ];
    validUrls.forEach((url) => {
      expect(isYouTubeVideoUrl(url)).toBe(true);
    });
  });

  test('returns false for invalid YouTube video URLs', () => {
    const invalidUrls = [
      'https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw',
      'https://www.youtube.com/results?search_query=cats',
      'https://www.youtube.com/',
      'https://youtu.be/',
    ];
    invalidUrls.forEach((url) => {
      expect(isYouTubeVideoUrl(url)).toBe(false);
    });
  });

  test('returns false for non-YouTube URLs', () => {
    const nonYouTubeUrls = ['https://example.com', 'https://www.google.com/search?q=youtube'];
    nonYouTubeUrls.forEach((url) => {
      expect(isYouTubeVideoUrl(url)).toBe(false);
    });
  });
});

describe('getVideoId', () => {
  test('returns video ID for valid YouTube video URLs', () => {
    const urlsAndIds = [
      ['https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'dQw4w9WgXcQ'],
      ['https://youtu.be/dQw4w9WgXcQ', 'dQw4w9WgXcQ'],
      ['https://youtu.be/dQw4w9WgXcQ?t=10s', 'dQw4w9WgXcQ'],
      ['https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=10s', 'dQw4w9WgXcQ'],
      ['https://www.youtube.com/embed/dQw4w9WgXcQ', 'dQw4w9WgXcQ'],
      ['https://www.youtube.com/embed/dQw4w9WgXcQ?start=10', 'dQw4w9WgXcQ'],
      [
        'https://www.youtube.com/watch?v=ngNhpdaT5V0&list=PLxai42gkPeMN1nTZFD4PEc96sMI8mdT03&index=3',
        'ngNhpdaT5V0',
      ],
    ];
    urlsAndIds.forEach(([url, expectedId]) => {
      expect(getVideoId(url)).toBe(expectedId);
    });
  });

  test('returns null for invalid or non-YouTube URLs', () => {
    const invalidUrls = [
      'https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw',
      'https://www.youtube.com/results?search_query=cats',
      'https://www.youtube.com/',
      'https://youtu.be/',
      'https://example.com',
      'https://www.google.com/search?q=youtube',
    ];
    invalidUrls.forEach((url) => {
      expect(getVideoId(url)).toBe(null);
    });
  });
});
0
0
2

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
0
0