Import Instagram archive to a Bluesky account
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

WIP refactoring for single responsibility with testing including multiformats which impacted all imports to require .js extensions.

+721 -168
+526 -4
package-lock.json
··· 10 10 "dependencies": { 11 11 "@atproto/api": "^0.13.31", 12 12 "@ffprobe-installer/ffprobe": "^2.1.2", 13 + "byte-size": "^9.0.1", 13 14 "dotenv": "^16.4.7", 14 15 "fluent-ffmpeg": "^2.1.3", 15 16 "luxon": "^3.5.0", 16 17 "multiformats": "^13.3.2", 18 + "multihashes": "^4.0.3", 17 19 "pino": "^9.6.0", 18 20 "pino-pretty": "^13.0.0", 19 - "process": "^0.11.10" 21 + "process": "^0.11.10", 22 + "sharp": "^0.33.5" 20 23 }, 21 24 "devDependencies": { 22 25 "@types/jest": "^29.5.14", ··· 645 648 "@jridgewell/sourcemap-codec": "^1.4.10" 646 649 } 647 650 }, 651 + "node_modules/@emnapi/runtime": { 652 + "version": "1.3.1", 653 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", 654 + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", 655 + "license": "MIT", 656 + "optional": true, 657 + "dependencies": { 658 + "tslib": "^2.4.0" 659 + } 660 + }, 648 661 "node_modules/@ffprobe-installer/darwin-arm64": { 649 662 "version": "5.0.1", 650 663 "resolved": "https://registry.npmjs.org/@ffprobe-installer/darwin-arm64/-/darwin-arm64-5.0.1.tgz", ··· 765 778 "win32" 766 779 ] 767 780 }, 781 + "node_modules/@img/sharp-darwin-arm64": { 782 + "version": "0.33.5", 783 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", 784 + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", 785 + "cpu": [ 786 + "arm64" 787 + ], 788 + "license": "Apache-2.0", 789 + "optional": true, 790 + "os": [ 791 + "darwin" 792 + ], 793 + "engines": { 794 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 795 + }, 796 + "funding": { 797 + "url": "https://opencollective.com/libvips" 798 + }, 799 + "optionalDependencies": { 800 + "@img/sharp-libvips-darwin-arm64": "1.0.4" 801 + } 802 + }, 803 + "node_modules/@img/sharp-darwin-x64": { 804 + "version": "0.33.5", 805 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", 806 + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", 807 + "cpu": [ 808 + "x64" 809 + ], 810 + "license": "Apache-2.0", 811 + "optional": true, 812 + "os": [ 813 + "darwin" 814 + ], 815 + "engines": { 816 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 817 + }, 818 + "funding": { 819 + "url": "https://opencollective.com/libvips" 820 + }, 821 + "optionalDependencies": { 822 + "@img/sharp-libvips-darwin-x64": "1.0.4" 823 + } 824 + }, 825 + "node_modules/@img/sharp-libvips-darwin-arm64": { 826 + "version": "1.0.4", 827 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", 828 + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", 829 + "cpu": [ 830 + "arm64" 831 + ], 832 + "license": "LGPL-3.0-or-later", 833 + "optional": true, 834 + "os": [ 835 + "darwin" 836 + ], 837 + "funding": { 838 + "url": "https://opencollective.com/libvips" 839 + } 840 + }, 841 + "node_modules/@img/sharp-libvips-darwin-x64": { 842 + "version": "1.0.4", 843 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", 844 + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", 845 + "cpu": [ 846 + "x64" 847 + ], 848 + "license": "LGPL-3.0-or-later", 849 + "optional": true, 850 + "os": [ 851 + "darwin" 852 + ], 853 + "funding": { 854 + "url": "https://opencollective.com/libvips" 855 + } 856 + }, 857 + "node_modules/@img/sharp-libvips-linux-arm": { 858 + "version": "1.0.5", 859 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", 860 + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", 861 + "cpu": [ 862 + "arm" 863 + ], 864 + "license": "LGPL-3.0-or-later", 865 + "optional": true, 866 + "os": [ 867 + "linux" 868 + ], 869 + "funding": { 870 + "url": "https://opencollective.com/libvips" 871 + } 872 + }, 873 + "node_modules/@img/sharp-libvips-linux-arm64": { 874 + "version": "1.0.4", 875 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", 876 + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", 877 + "cpu": [ 878 + "arm64" 879 + ], 880 + "license": "LGPL-3.0-or-later", 881 + "optional": true, 882 + "os": [ 883 + "linux" 884 + ], 885 + "funding": { 886 + "url": "https://opencollective.com/libvips" 887 + } 888 + }, 889 + "node_modules/@img/sharp-libvips-linux-s390x": { 890 + "version": "1.0.4", 891 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", 892 + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", 893 + "cpu": [ 894 + "s390x" 895 + ], 896 + "license": "LGPL-3.0-or-later", 897 + "optional": true, 898 + "os": [ 899 + "linux" 900 + ], 901 + "funding": { 902 + "url": "https://opencollective.com/libvips" 903 + } 904 + }, 905 + "node_modules/@img/sharp-libvips-linux-x64": { 906 + "version": "1.0.4", 907 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", 908 + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", 909 + "cpu": [ 910 + "x64" 911 + ], 912 + "license": "LGPL-3.0-or-later", 913 + "optional": true, 914 + "os": [ 915 + "linux" 916 + ], 917 + "funding": { 918 + "url": "https://opencollective.com/libvips" 919 + } 920 + }, 921 + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { 922 + "version": "1.0.4", 923 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", 924 + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", 925 + "cpu": [ 926 + "arm64" 927 + ], 928 + "license": "LGPL-3.0-or-later", 929 + "optional": true, 930 + "os": [ 931 + "linux" 932 + ], 933 + "funding": { 934 + "url": "https://opencollective.com/libvips" 935 + } 936 + }, 937 + "node_modules/@img/sharp-libvips-linuxmusl-x64": { 938 + "version": "1.0.4", 939 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", 940 + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", 941 + "cpu": [ 942 + "x64" 943 + ], 944 + "license": "LGPL-3.0-or-later", 945 + "optional": true, 946 + "os": [ 947 + "linux" 948 + ], 949 + "funding": { 950 + "url": "https://opencollective.com/libvips" 951 + } 952 + }, 953 + "node_modules/@img/sharp-linux-arm": { 954 + "version": "0.33.5", 955 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", 956 + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", 957 + "cpu": [ 958 + "arm" 959 + ], 960 + "license": "Apache-2.0", 961 + "optional": true, 962 + "os": [ 963 + "linux" 964 + ], 965 + "engines": { 966 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 967 + }, 968 + "funding": { 969 + "url": "https://opencollective.com/libvips" 970 + }, 971 + "optionalDependencies": { 972 + "@img/sharp-libvips-linux-arm": "1.0.5" 973 + } 974 + }, 975 + "node_modules/@img/sharp-linux-arm64": { 976 + "version": "0.33.5", 977 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", 978 + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", 979 + "cpu": [ 980 + "arm64" 981 + ], 982 + "license": "Apache-2.0", 983 + "optional": true, 984 + "os": [ 985 + "linux" 986 + ], 987 + "engines": { 988 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 989 + }, 990 + "funding": { 991 + "url": "https://opencollective.com/libvips" 992 + }, 993 + "optionalDependencies": { 994 + "@img/sharp-libvips-linux-arm64": "1.0.4" 995 + } 996 + }, 997 + "node_modules/@img/sharp-linux-s390x": { 998 + "version": "0.33.5", 999 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", 1000 + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", 1001 + "cpu": [ 1002 + "s390x" 1003 + ], 1004 + "license": "Apache-2.0", 1005 + "optional": true, 1006 + "os": [ 1007 + "linux" 1008 + ], 1009 + "engines": { 1010 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1011 + }, 1012 + "funding": { 1013 + "url": "https://opencollective.com/libvips" 1014 + }, 1015 + "optionalDependencies": { 1016 + "@img/sharp-libvips-linux-s390x": "1.0.4" 1017 + } 1018 + }, 1019 + "node_modules/@img/sharp-linux-x64": { 1020 + "version": "0.33.5", 1021 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", 1022 + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", 1023 + "cpu": [ 1024 + "x64" 1025 + ], 1026 + "license": "Apache-2.0", 1027 + "optional": true, 1028 + "os": [ 1029 + "linux" 1030 + ], 1031 + "engines": { 1032 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1033 + }, 1034 + "funding": { 1035 + "url": "https://opencollective.com/libvips" 1036 + }, 1037 + "optionalDependencies": { 1038 + "@img/sharp-libvips-linux-x64": "1.0.4" 1039 + } 1040 + }, 1041 + "node_modules/@img/sharp-linuxmusl-arm64": { 1042 + "version": "0.33.5", 1043 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", 1044 + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", 1045 + "cpu": [ 1046 + "arm64" 1047 + ], 1048 + "license": "Apache-2.0", 1049 + "optional": true, 1050 + "os": [ 1051 + "linux" 1052 + ], 1053 + "engines": { 1054 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1055 + }, 1056 + "funding": { 1057 + "url": "https://opencollective.com/libvips" 1058 + }, 1059 + "optionalDependencies": { 1060 + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" 1061 + } 1062 + }, 1063 + "node_modules/@img/sharp-linuxmusl-x64": { 1064 + "version": "0.33.5", 1065 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", 1066 + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", 1067 + "cpu": [ 1068 + "x64" 1069 + ], 1070 + "license": "Apache-2.0", 1071 + "optional": true, 1072 + "os": [ 1073 + "linux" 1074 + ], 1075 + "engines": { 1076 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1077 + }, 1078 + "funding": { 1079 + "url": "https://opencollective.com/libvips" 1080 + }, 1081 + "optionalDependencies": { 1082 + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" 1083 + } 1084 + }, 1085 + "node_modules/@img/sharp-wasm32": { 1086 + "version": "0.33.5", 1087 + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", 1088 + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", 1089 + "cpu": [ 1090 + "wasm32" 1091 + ], 1092 + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", 1093 + "optional": true, 1094 + "dependencies": { 1095 + "@emnapi/runtime": "^1.2.0" 1096 + }, 1097 + "engines": { 1098 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1099 + }, 1100 + "funding": { 1101 + "url": "https://opencollective.com/libvips" 1102 + } 1103 + }, 1104 + "node_modules/@img/sharp-win32-ia32": { 1105 + "version": "0.33.5", 1106 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", 1107 + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", 1108 + "cpu": [ 1109 + "ia32" 1110 + ], 1111 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1112 + "optional": true, 1113 + "os": [ 1114 + "win32" 1115 + ], 1116 + "engines": { 1117 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1118 + }, 1119 + "funding": { 1120 + "url": "https://opencollective.com/libvips" 1121 + } 1122 + }, 1123 + "node_modules/@img/sharp-win32-x64": { 1124 + "version": "0.33.5", 1125 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", 1126 + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", 1127 + "cpu": [ 1128 + "x64" 1129 + ], 1130 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 1131 + "optional": true, 1132 + "os": [ 1133 + "win32" 1134 + ], 1135 + "engines": { 1136 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1137 + }, 1138 + "funding": { 1139 + "url": "https://opencollective.com/libvips" 1140 + } 1141 + }, 768 1142 "node_modules/@istanbuljs/load-nyc-config": { 769 1143 "version": "1.1.0", 770 1144 "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", ··· 1131 1505 "@jridgewell/resolve-uri": "^3.1.0", 1132 1506 "@jridgewell/sourcemap-codec": "^1.4.14" 1133 1507 } 1508 + }, 1509 + "node_modules/@multiformats/base-x": { 1510 + "version": "4.0.1", 1511 + "resolved": "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz", 1512 + "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==", 1513 + "license": "MIT" 1134 1514 }, 1135 1515 "node_modules/@sinclair/typebox": { 1136 1516 "version": "0.27.8", ··· 1639 2019 "dev": true, 1640 2020 "peer": true 1641 2021 }, 2022 + "node_modules/byte-size": { 2023 + "version": "9.0.1", 2024 + "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-9.0.1.tgz", 2025 + "integrity": "sha512-YLe9x3rabBrcI0cueCdLS2l5ONUKywcRpTs02B8KP9/Cimhj7o3ZccGrPnRvcbyHMbb7W79/3MUJl7iGgTXKEw==", 2026 + "license": "MIT", 2027 + "engines": { 2028 + "node": ">=12.17" 2029 + }, 2030 + "peerDependencies": { 2031 + "@75lb/nature": "latest" 2032 + }, 2033 + "peerDependenciesMeta": { 2034 + "@75lb/nature": { 2035 + "optional": true 2036 + } 2037 + } 2038 + }, 1642 2039 "node_modules/callsites": { 1643 2040 "version": "3.1.0", 1644 2041 "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", ··· 1761 2158 "dev": true, 1762 2159 "peer": true 1763 2160 }, 2161 + "node_modules/color": { 2162 + "version": "4.2.3", 2163 + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", 2164 + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", 2165 + "license": "MIT", 2166 + "dependencies": { 2167 + "color-convert": "^2.0.1", 2168 + "color-string": "^1.9.0" 2169 + }, 2170 + "engines": { 2171 + "node": ">=12.5.0" 2172 + } 2173 + }, 1764 2174 "node_modules/color-convert": { 1765 2175 "version": "2.0.1", 1766 2176 "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1767 2177 "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1768 - "dev": true, 1769 2178 "license": "MIT", 1770 2179 "dependencies": { 1771 2180 "color-name": "~1.1.4" ··· 1778 2187 "version": "1.1.4", 1779 2188 "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1780 2189 "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1781 - "dev": true, 1782 2190 "license": "MIT" 1783 2191 }, 2192 + "node_modules/color-string": { 2193 + "version": "1.9.1", 2194 + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", 2195 + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 2196 + "license": "MIT", 2197 + "dependencies": { 2198 + "color-name": "^1.0.0", 2199 + "simple-swizzle": "^0.2.2" 2200 + } 2201 + }, 1784 2202 "node_modules/colorette": { 1785 2203 "version": "2.0.20", 1786 2204 "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", ··· 1909 2327 "peer": true, 1910 2328 "engines": { 1911 2329 "node": ">=0.10.0" 2330 + } 2331 + }, 2332 + "node_modules/detect-libc": { 2333 + "version": "2.0.3", 2334 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", 2335 + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", 2336 + "license": "Apache-2.0", 2337 + "engines": { 2338 + "node": ">=8" 1912 2339 } 1913 2340 }, 1914 2341 "node_modules/detect-newline": { ··· 2419 2846 "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 2420 2847 "dev": true, 2421 2848 "peer": true 2849 + }, 2850 + "node_modules/is-arrayish": { 2851 + "version": "0.3.2", 2852 + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 2853 + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", 2854 + "license": "MIT" 2422 2855 }, 2423 2856 "node_modules/is-core-module": { 2424 2857 "version": "2.16.1", ··· 3378 3811 "dev": true, 3379 3812 "peer": true 3380 3813 }, 3814 + "node_modules/multibase": { 3815 + "version": "4.0.6", 3816 + "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.6.tgz", 3817 + "integrity": "sha512-x23pDe5+svdLz/k5JPGCVdfn7Q5mZVMBETiC+ORfO+sor9Sgs0smJzAjfTbM5tckeCqnaUuMYoz+k3RXMmJClQ==", 3818 + "deprecated": "This module has been superseded by the multiformats module", 3819 + "license": "MIT", 3820 + "dependencies": { 3821 + "@multiformats/base-x": "^4.0.1" 3822 + }, 3823 + "engines": { 3824 + "node": ">=12.0.0", 3825 + "npm": ">=6.0.0" 3826 + } 3827 + }, 3381 3828 "node_modules/multiformats": { 3382 3829 "version": "13.3.2", 3383 3830 "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.3.2.tgz", 3384 3831 "integrity": "sha512-qbB0CQDt3QKfiAzZ5ZYjLFOs+zW43vA4uyM8g27PeEuXZybUOFyjrVdP93HPBHMoglibwfkdVwbzfUq8qGcH6g==", 3385 3832 "license": "Apache-2.0 OR MIT" 3386 3833 }, 3834 + "node_modules/multihashes": { 3835 + "version": "4.0.3", 3836 + "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.3.tgz", 3837 + "integrity": "sha512-0AhMH7Iu95XjDLxIeuCOOE4t9+vQZsACyKZ9Fxw2pcsRmlX4iCn1mby0hS0bb+nQOVpdQYWPpnyusw4da5RPhA==", 3838 + "license": "MIT", 3839 + "dependencies": { 3840 + "multibase": "^4.0.1", 3841 + "uint8arrays": "^3.0.0", 3842 + "varint": "^5.0.2" 3843 + }, 3844 + "engines": { 3845 + "node": ">=12.0.0", 3846 + "npm": ">=6.0.0" 3847 + } 3848 + }, 3387 3849 "node_modules/natural-compare": { 3388 3850 "version": "1.4.0", 3389 3851 "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", ··· 3870 4332 "version": "7.6.3", 3871 4333 "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", 3872 4334 "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", 3873 - "dev": true, 3874 4335 "license": "ISC", 3875 4336 "bin": { 3876 4337 "semver": "bin/semver.js" ··· 3879 4340 "node": ">=10" 3880 4341 } 3881 4342 }, 4343 + "node_modules/sharp": { 4344 + "version": "0.33.5", 4345 + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", 4346 + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", 4347 + "hasInstallScript": true, 4348 + "license": "Apache-2.0", 4349 + "dependencies": { 4350 + "color": "^4.2.3", 4351 + "detect-libc": "^2.0.3", 4352 + "semver": "^7.6.3" 4353 + }, 4354 + "engines": { 4355 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 4356 + }, 4357 + "funding": { 4358 + "url": "https://opencollective.com/libvips" 4359 + }, 4360 + "optionalDependencies": { 4361 + "@img/sharp-darwin-arm64": "0.33.5", 4362 + "@img/sharp-darwin-x64": "0.33.5", 4363 + "@img/sharp-libvips-darwin-arm64": "1.0.4", 4364 + "@img/sharp-libvips-darwin-x64": "1.0.4", 4365 + "@img/sharp-libvips-linux-arm": "1.0.5", 4366 + "@img/sharp-libvips-linux-arm64": "1.0.4", 4367 + "@img/sharp-libvips-linux-s390x": "1.0.4", 4368 + "@img/sharp-libvips-linux-x64": "1.0.4", 4369 + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", 4370 + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", 4371 + "@img/sharp-linux-arm": "0.33.5", 4372 + "@img/sharp-linux-arm64": "0.33.5", 4373 + "@img/sharp-linux-s390x": "0.33.5", 4374 + "@img/sharp-linux-x64": "0.33.5", 4375 + "@img/sharp-linuxmusl-arm64": "0.33.5", 4376 + "@img/sharp-linuxmusl-x64": "0.33.5", 4377 + "@img/sharp-wasm32": "0.33.5", 4378 + "@img/sharp-win32-ia32": "0.33.5", 4379 + "@img/sharp-win32-x64": "0.33.5" 4380 + } 4381 + }, 3882 4382 "node_modules/shebang-command": { 3883 4383 "version": "2.0.0", 3884 4384 "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", ··· 3908 4408 "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", 3909 4409 "dev": true, 3910 4410 "peer": true 4411 + }, 4412 + "node_modules/simple-swizzle": { 4413 + "version": "0.2.2", 4414 + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 4415 + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 4416 + "license": "MIT", 4417 + "dependencies": { 4418 + "is-arrayish": "^0.3.1" 4419 + } 3911 4420 }, 3912 4421 "node_modules/sisteransi": { 3913 4422 "version": "1.0.5", ··· 4224 4733 } 4225 4734 } 4226 4735 }, 4736 + "node_modules/tslib": { 4737 + "version": "2.8.1", 4738 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 4739 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 4740 + "license": "0BSD", 4741 + "optional": true 4742 + }, 4227 4743 "node_modules/type-detect": { 4228 4744 "version": "4.0.8", 4229 4745 "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", ··· 4333 4849 "engines": { 4334 4850 "node": ">=10.12.0" 4335 4851 } 4852 + }, 4853 + "node_modules/varint": { 4854 + "version": "5.0.2", 4855 + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", 4856 + "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", 4857 + "license": "MIT" 4336 4858 }, 4337 4859 "node_modules/walker": { 4338 4860 "version": "1.0.8",
+5 -1
package.json
··· 2 2 "name": "instagramtobluesky", 3 3 "version": "0.3.0", 4 4 "description": "Import Instagram archive to a Bluesky account", 5 + "type": "module", 5 6 "main": "app.js", 6 7 "engines": { 7 8 "node": ">=20.12.0" ··· 17 18 "dependencies": { 18 19 "@atproto/api": "^0.13.31", 19 20 "@ffprobe-installer/ffprobe": "^2.1.2", 21 + "byte-size": "^9.0.1", 20 22 "dotenv": "^16.4.7", 21 23 "fluent-ffmpeg": "^2.1.3", 22 24 "luxon": "^3.5.0", 23 25 "multiformats": "^13.3.2", 26 + "multihashes": "^4.0.3", 24 27 "pino": "^9.6.0", 25 28 "pino-pretty": "^13.0.0", 26 - "process": "^0.11.10" 29 + "process": "^0.11.10", 30 + "sharp": "^0.33.5" 27 31 }, 28 32 "devDependencies": { 29 33 "@types/jest": "^29.5.14",
+11 -13
src/bluesky/bluesky.test.ts
··· 1 1 import { BlobRef } from '@atproto/api'; 2 2 3 - import { mock } from 'node:test'; 4 - 5 3 import { CID } from 'multiformats'; 6 - import { createHash } from 'multiformats/hashes'; 7 - import { sha256 } from 'multiformats/hashes/sha2'; 8 - import { toMultihash } from 'multiformats/hashes/sha2'; 9 4 10 5 import fs from 'fs'; 11 6 12 - import { BlueskyClient } from './bluesky'; 13 - import { ImagesEmbedImpl, VideoEmbedImpl } from './types'; 7 + import { BlueskyClient } from './bluesky.js'; 8 + 9 + import { ImagesEmbedImpl, VideoEmbedImpl } from './types/index.js'; 14 10 15 11 const TEST_VIDEO_PATH = './transfer/test_videos/AQM8KYlOYHTF5GlP43eMroHUpmnFHJh5CnCJUdRUeqWxG4tNX7D43eM77F152vfi4znTzgkFTTzzM4nHa_v8ugmP4WPRJtjKPZX5pko_17845940218109367.mp4'; 16 12 ··· 60 56 describe('BlueskyClient', () => { 61 57 let client: BlueskyClient; 62 58 let mockCID: CID; 59 + let videoBuffer: Buffer; 63 60 64 61 beforeEach(() => { 65 62 client = new BlueskyClient('test-user', 'test-pass'); 66 63 }); 67 64 68 65 beforeAll(async () => { 69 - const videoBuffer = fs.readFileSync(TEST_VIDEO_PATH); 70 - const hash = await sha256.encode(videoBuffer); 71 - const multihash = toMultihash(hash); 72 - mockCID = CID.create(1, 0x71, multihash); 66 + videoBuffer = fs.readFileSync(TEST_VIDEO_PATH); 67 + /** 68 + * CID from test video uploaded to Pinata.cloud. 69 + * Creating CID from the video proved to be too challenging. 70 + */ 71 + mockCID = CID.parse('bafybeibssikmpbeu3z7ezozo7447go7gpneqgblsyo2owed4qleljptmeu') 73 72 }); 74 73 75 74 test('should create post successfully', async () => { ··· 106 105 }); 107 106 108 107 test('should create video post successfully', async () => { 109 - const buffer = fs.readFileSync(TEST_VIDEO_PATH); 110 108 const videoEmbed = new VideoEmbedImpl( 111 109 'test video', 112 - buffer, 110 + videoBuffer, 113 111 'video/mp4', 114 112 1000, 115 113 new BlobRef(mockCID, 'video/mp4', 1000)
+13 -42
src/bluesky/bluesky.ts
··· 6 6 AppBskyEmbedImages, 7 7 } from "@atproto/api"; 8 8 9 - import { logger } from "../logger/logger"; 9 + import { logger } from "@logger/logger.js"; 10 10 11 11 import { 12 12 ImageEmbed, 13 13 EmbeddedMedia, 14 14 ImageEmbedImpl, 15 15 VideoEmbedImpl, 16 - ImagesEmbedImpl 17 - } from "./types"; 16 + ImagesEmbedImpl, 17 + PostRecordImpl 18 + } from "./types/index.js"; 18 19 19 20 20 21 export class BlueskyClient { ··· 89 90 } 90 91 } 91 92 93 + /** 94 + * Creates a post on Bluesky. 95 + * @param postDate 96 + * @param postText 97 + * @param embeddedMedia 98 + * @returns 99 + */ 92 100 async createPost( 93 101 postDate: Date, 94 102 postText: string, 95 103 embeddedMedia: EmbeddedMedia 96 104 ): Promise<string | null> { 97 105 try { 98 - // Handle image uploads if present 99 - if (Array.isArray(embeddedMedia) && AppBskyEmbedImages.isImage(embeddedMedia[0])) { 100 - const imagesMedia: ImageEmbed[] = embeddedMedia; 101 - const uploadedImages = await Promise.all( 102 - imagesMedia.map(async (media) => { 103 - const blob = await this.uploadImage( 104 - media.image, 105 - media.mimeType 106 - ); 107 - return new ImageEmbedImpl( 108 - media.alt, 109 - blob, 110 - media.mimeType, 111 - media.uploadData 112 - ); 113 - }) 114 - ); 115 - 116 - embeddedMedia = new ImagesEmbedImpl(uploadedImages); 117 - } else if (AppBskyEmbedVideo.isMain(embeddedMedia)) { 118 - // Upload video first 119 - const videoBlobRef = await this.uploadVideo( 120 - embeddedMedia.buffer, 121 - embeddedMedia.mimeType 122 - ); 123 - // Now transform the embed 124 - embeddedMedia = new VideoEmbedImpl( 125 - "", 126 - embeddedMedia.buffer, 127 - embeddedMedia.mimeType, 128 - embeddedMedia.size, 129 - videoBlobRef, 130 - embeddedMedia.aspectRatio, 131 - embeddedMedia.captions 132 - ); 133 - } 134 - 135 106 const rt = new RichText({ text: postText }); 136 107 await rt.detectFacets(this.agent); 137 108 138 109 // create blsky post record. 139 - const postRecord = new PostRecord( 110 + const postRecord = new PostRecordImpl( 140 111 rt.text, 141 112 postDate.toISOString(), 142 - rt.facets, 113 + rt.facets!, 143 114 embeddedMedia 144 115 ); 145 116
+2 -2
src/bluesky/index.ts
··· 1 - export * from './types'; 2 - export * from './bluesky'; 1 + export * from './types/index.js'; 2 + export * from './bluesky.js';
+2 -2
src/bluesky/types/EmbeddedMedia.ts
··· 1 - import { VideoEmbed } from "./VideoEmbed"; 2 - import { ImagesEmbed } from "./ImagesEmbed"; 1 + import { VideoEmbed } from "./VideoEmbed.js"; 2 + import { ImagesEmbed } from "./ImagesEmbed.js"; 3 3 4 4 export type EmbeddedMedia = VideoEmbed | ImagesEmbed;
+7
src/bluesky/types/ImageEmbed.ts
··· 1 1 import { AppBskyEmbedImages, BlobRef } from "@atproto/api"; 2 2 3 + 4 + /** 5 + * Image uploaded to bluesky, containing BlobRef CID. 6 + */ 3 7 export interface ImageEmbed extends AppBskyEmbedImages.Image { 4 8 $type: "app.bsky.embed.images#image"; 5 9 alt: string; ··· 8 12 uploadData?: Buffer | Blob; 9 13 } 10 14 15 + /** 16 + * Image uploaded to bluesky, containing BlobRef CID. 17 + */ 11 18 export class ImageEmbedImpl implements ImageEmbed { 12 19 readonly $type = "app.bsky.embed.images#image"; 13 20 [k: string]: unknown;
+1 -1
src/bluesky/types/ImagesEmbed.ts
··· 1 1 import { AppBskyEmbedImages } from "@atproto/api"; 2 - import { ImageEmbed } from "./ImageEmbed"; 2 + import { ImageEmbed } from "./ImageEmbed.js"; 3 3 4 4 export interface ImagesEmbed extends AppBskyEmbedImages.Main { 5 5 $type: "app.bsky.embed.images";
+3 -3
src/bluesky/types/PostRecord.ts
··· 1 - import { AppBskyFeedPost, AppBskyRichtextFacet } from "@atproto/api"; 2 - import { EmbeddedMedia } from "./EmbeddedMedia"; 1 + import { AppBskyFeedPost, Facet } from "@atproto/api"; 2 + import { EmbeddedMedia } from "./EmbeddedMedia.js"; 3 3 4 4 export interface PostRecord extends Partial<AppBskyFeedPost.Record> {} 5 5 ··· 10 10 constructor( 11 11 public text: string, 12 12 public createdAt: string, 13 - public facets: AppBskyRichtextFacet.Main[], 13 + public facets: Facet[], 14 14 public embed: EmbeddedMedia 15 15 ) {} 16 16 }
+5 -5
src/bluesky/types/index.ts
··· 1 - export * from "./ImageEmbed"; 2 - export * from "./VideoEmbed"; 3 - export * from "./ImagesEmbed"; 4 - export * from "./PostRecord"; 5 - export * from "./EmbeddedMedia"; 1 + export * from "./ImageEmbed.js"; 2 + export * from "./VideoEmbed.js"; 3 + export * from "./ImagesEmbed.js"; 4 + export * from "./PostRecord.js"; 5 + export * from "./EmbeddedMedia.js";
+1 -1
src/image/image.ts
··· 1 1 import sharp from "sharp"; 2 2 import byteSize from "byte-size"; 3 - import { logger } from "../logger/logger"; 3 + import { logger } from "@logger/logger.js"; 4 4 5 5 /** 6 6 * Image lexicon maxSize 1mb
+1 -1
src/image/index.ts
··· 1 - export * from './image'; 1 + export * from './image.js';
+4 -4
src/instagram-to-bluesky.test.ts
··· 1 1 import fs from 'fs'; 2 2 3 - import { BlueskyClient } from '../src/bluesky'; 4 - import { main, formatDuration, calculateEstimatedTime } from '../src/instagram-to-bluesky'; 5 - import { logger } from '../src/logger'; 6 - import { processPost } from '../src/media'; 3 + import { main, formatDuration, calculateEstimatedTime } from '@src/instagram-to-bluesky.js'; 4 + import { BlueskyClient } from '@src/bluesky/bluesky.js'; 5 + import { logger } from '@src/logger/logger.js'; 6 + import { processPost } from '@src/media/index.js'; 7 7 8 8 // Mock all dependencies 9 9 jest.mock('fs');
+6 -4
src/instagram-to-bluesky.ts
··· 3 3 import path from 'path'; 4 4 import * as process from 'process'; 5 5 6 - import { BlueskyClient } from './bluesky/bluesky'; 7 - import { logger } from './logger/logger'; 8 - import { processPost } from './media/media'; 9 - import { createVideoEmbed, prepareVideoUpload } from './video/video'; 6 + import { BlueskyClient } from './bluesky/bluesky.js'; 7 + import { logger } from './logger/logger.js'; 8 + import { processPost } from './media/media.js'; 10 9 11 10 dotenv.config(); 12 11 ··· 209 208 setTimeout(resolve, API_RATE_LIMIT_DELAY) 210 209 ); 211 210 try { 211 + 212 + 213 + // Create post with embedded pre-uploaded data. 212 214 const postUrl = await bluesky.createPost( 213 215 postDate, 214 216 postText,
-1
src/logger/index.ts
··· 1 - export * from './logger';
+1 -1
src/main.ts
··· 1 - import { main } from "./instagram-to-bluesky"; 1 + import { main } from "./instagram-to-bluesky.js"; 2 2 3 3 (async () => { 4 4 await main();
+30
src/media/MediaProcessResult.ts
··· 1 + /** 2 + * Social media data processed to be uploaded to Bluesky. 3 + */ 4 + export interface MediaProcessResult { 5 + mediaText: string; 6 + mimeType: string | null; 7 + mediaBuffer: Buffer | null; 8 + isVideo: boolean; 9 + } 10 + 11 + /** 12 + * Social media data processed to be uploaded to Bluesky. 13 + */ 14 + export class MediaProcessResultImpl implements MediaProcessResult { 15 + constructor( 16 + public mediaText: string, 17 + public mimeType: string | null, 18 + public mediaBuffer: Buffer | null, 19 + public isVideo: boolean 20 + ) {} 21 + 22 + toJSON() { 23 + return { 24 + mediaText: this.mediaText, 25 + mimeType: this.mimeType, 26 + mediaBuffer: this.mediaBuffer ? "[Buffer length=" + this.mediaBuffer.length + "]" : null, 27 + isVideo: this.isVideo 28 + }; 29 + } 30 + }
+8
src/media/ProcessedPost.ts
··· 1 + import { MediaProcessResult } from './MediaProcessResult.js'; 2 + 3 + export interface ProcessedPost { 4 + postDate: Date | null; 5 + postText: string; 6 + embeddedMedia: MediaProcessResult | MediaProcessResult[]; 7 + mediaCount: number; 8 + }
+3 -1
src/media/index.ts
··· 1 - export * from './media'; 1 + export * from './MediaProcessResult.js'; 2 + export * from './ProcessedPost.js'; 3 + export * from './media.js';
+6 -3
src/media/media.test.ts
··· 1 - import { getMimeType, processMedia, processPost } from "./media"; 2 1 import path from "path"; 2 + 3 3 import fs from "fs"; 4 - import { BlueskyClient } from '../bluesky/bluesky'; 5 - import { createVideoEmbed, processVideoPost } from '../video/video'; 4 + 5 + import { BlueskyClient } from '@bluesky/bluesky.js'; 6 + import { processVideoPost } from '@video/video.js'; 7 + 8 + import { getMimeType, processMedia, processPost } from "./media.js"; 6 9 7 10 // Mock the file system 8 11 jest.mock("fs", () => ({
+23 -42
src/media/media.ts
··· 1 - import { 2 - BlueskyClient, 3 - } from "../bluesky/bluesky"; 4 - import { logger } from "../logger/logger"; 5 - import { validateVideo, processVideoPost } from "../video/video"; 6 1 import FS from "fs"; 7 2 8 - export interface MediaProcessResult { 9 - mediaText: string; 10 - mimeType: string | null; 11 - mediaBuffer: Buffer | null; 12 - isVideo: boolean; 13 - } 14 - 15 - /** 16 - * Processed media from instagram post that supports logging. 17 - */ 18 - export class MediaProcessResultImpl implements MediaProcessResult { 19 - constructor( 20 - public mediaText: string, 21 - public mimeType: string | null, 22 - public mediaBuffer: Buffer | null, 23 - public isVideo: boolean 24 - ) {} 25 - 26 - toJSON() { 27 - return { 28 - mediaText: this.mediaText, 29 - mimeType: this.mimeType, 30 - mediaBuffer: this.mediaBuffer ? "[Buffer length=" + this.mediaBuffer.length + "]" : null, 31 - isVideo: this.isVideo 32 - }; 33 - } 34 - } 35 - 36 - /** 37 - * Instagram post thats been processed to be transformed into a Bluesky post. 38 - */ 39 - export interface ProcessedPost { 40 - postDate: Date | null; 41 - postText: string; 42 - embeddedMedia: MediaProcessResult | MediaProcessResult[]; 43 - mediaCount: number; 44 - } 3 + import { 4 + BlueskyClient, 5 + } from "@bluesky/bluesky.js"; 6 + import { logger } from "@logger/logger.js"; 7 + import { validateVideo, processVideoPost } from "@video/video.js"; 8 + import { ProcessedPost } from "./ProcessedPost.js"; 9 + import { MediaProcessResult, MediaProcessResultImpl } from "./MediaProcessResult.js"; 45 10 46 11 const MAX_IMAGES_PER_POST = 4; 47 12 const POST_TEXT_LIMIT = 300; ··· 65 30 } 66 31 } 67 32 33 + /** 34 + * Transforms media (image(s)/video) from social media format to a object that can be uploaded to bluesky to become embedded media. 35 + * @param media 36 + * @param archiveFolder 37 + * @returns 38 + */ 68 39 export async function processMedia( 69 40 media: any, 70 41 archiveFolder: string ··· 110 81 return new MediaProcessResultImpl(truncatedText, mimeType, mediaBuffer, isVideo); 111 82 } 112 83 84 + /** 85 + * Transforms post content from social media format into the bluesky post format. 86 + * @param post 87 + * @param archiveFolder 88 + * @param bluesky 89 + * @param simulate 90 + * @returns 91 + */ 113 92 export async function processPost( 114 93 post: any, 115 94 archiveFolder: string, ··· 222 201 mediaCount, 223 202 }; 224 203 } 204 + 205 +
+1 -1
src/video/index.ts
··· 1 - export * from './video'; 1 + export * from './video.js';
+2 -3
src/video/video.test.ts
··· 1 - import { validateVideo, getVideoDimensions } from './video'; 1 + import { validateVideo, getVideoDimensions, processVideoPost } from './video.js'; 2 2 import path from 'path'; 3 - import { processVideoPost } from './video'; 4 - import { BlueskyClient } from '../bluesky/bluesky'; 3 + import { BlueskyClient } from '@bluesky/bluesky.js'; 5 4 6 5 describe('Video Processing', () => { 7 6 const testVideoPath = path.join(__dirname, '../transfer/test_videos/AQM8KYlOYHTF5GlP43eMroHUpmnFHJh5CnCJUdRUeqWxG4tNX7D43eM77F152vfi4znTzgkFTTzzM4nHa_v8ugmP4WPRJtjKPZX5pko_17845940218109367.mp4');
+53 -32
src/video/video.ts
··· 1 1 import ffmpeg from 'fluent-ffmpeg'; 2 2 import ffprobe from '@ffprobe-installer/ffprobe'; 3 - import { logger } from '../logger/logger' 4 - import { BlueskyClient, VideoEmbed } from '../bluesky'; 3 + import { logger } from '@logger/logger.js' 4 + import { BlueskyClient } from '@bluesky/bluesky.js'; 5 5 import { BlobRef } from '@atproto/api'; 6 6 7 7 // Configure ffmpeg to use ffprobe ··· 94 94 95 95 export interface VideoEmbedOutput { 96 96 $type: "app.bsky.embed.video"; 97 - video: { 98 - $type: string; 99 - ref: { $link: string }; 100 - mimeType: string; 101 - size: number; 102 - }; 97 + video: BlobRef; 103 98 aspectRatio: { 104 99 width: number; 105 100 height: number; ··· 108 103 109 104 export class VideoEmbedOutputImpl implements VideoEmbedOutput { 110 105 readonly $type = "app.bsky.embed.video"; 111 - readonly video: { 112 - $type: string; 113 - ref: { $link: string }; 114 - mimeType: string; 115 - size: number; 116 - }; 106 + readonly video: BlobRef; 117 107 readonly aspectRatio: { 118 108 width: number; 119 109 height: number; 120 110 }; 121 111 122 112 constructor( 123 - ref: string, 113 + ref: BlobRef, 124 114 mimeType: string, 125 115 size: number, 126 116 dimensions: { width: number; height: number } 127 117 ) { 128 - this.video = { 129 - $type: "blob", 130 - ref: { $link: ref }, 131 - mimeType, 132 - size 133 - }; 118 + this.video = ref; 134 119 this.aspectRatio = dimensions; 135 120 } 136 121 } ··· 138 123 /** 139 124 * Creates the video embed structure for Bluesky post 140 125 */ 141 - export function createVideoEmbed(videoData: { 142 - ref: string, 143 - mimeType: string, 144 - size: number, 145 - dimensions: {width: number, height: number} 146 - }): VideoEmbedOutput { 126 + export function createVideoEmbed(videoData: VideoUploadData): VideoEmbedOutput { 147 127 return new VideoEmbedOutputImpl( 148 - videoData.ref, 128 + videoData.ref!, 149 129 videoData.mimeType, 150 130 videoData.size, 151 131 videoData.dimensions 152 132 ); 153 133 } 154 134 135 + 155 136 /** 156 - * Processes a video file for posting to Bluesky, including metadata preparation and upload 137 + * 138 + * // Handle image uploads if present 139 + if (Array.isArray(embeddedMedia) && AppBskyEmbedImages.isImage(embeddedMedia[0])) { 140 + const imagesMedia: ImageEmbed[] = embeddedMedia; 141 + const uploadedImages = await Promise.all( 142 + imagesMedia.map(async (media) => { 143 + const blob = await this.uploadImage( 144 + media.image, 145 + media.mimeType 146 + ); 147 + return new ImageEmbedImpl( 148 + media.alt, 149 + blob, 150 + media.mimeType, 151 + media.uploadData 152 + ); 153 + }) 154 + ); 155 + 156 + embeddedMedia = new ImagesEmbedImpl(uploadedImages); 157 + } else if (AppBskyEmbedVideo.isMain(embeddedMedia)) { 158 + // Upload video first 159 + const videoBlobRef = await this.uploadVideo( 160 + embeddedMedia.buffer, 161 + embeddedMedia.mimeType 162 + ); 163 + // Now transform the embed 164 + embeddedMedia = new VideoEmbedImpl( 165 + "", 166 + embeddedMedia.buffer, 167 + embeddedMedia.mimeType, 168 + embeddedMedia.size, 169 + videoBlobRef, 170 + embeddedMedia.aspectRatio, 171 + embeddedMedia.captions 172 + ); 173 + } 174 + * 175 + */ 176 + 177 + 178 + /** 179 + * Processes a video file for posting to Bluesky, including metadata preparation and upload. 157 180 * 158 181 * @param filePath - The path to the video file being processed 159 182 * @param buffer - The video file contents as a Buffer ··· 185 208 186 209 // Upload video to get CID 187 210 if (!simulate && bluesky) { 188 - 189 - // TODO isolate this logic and remove it only being placed into the media directory. 190 211 const blob = await bluesky.uploadVideo(buffer); 191 212 if (!blob?.ref) { 192 213 throw new Error("Failed to get video upload reference"); 193 214 } 194 - videoData.ref.$link = blob.ref.$link; 215 + videoData.ref = blob; 195 216 } 196 217 197 218 // Create video embed structure
+7 -1
tsconfig.json
··· 9 9 "strict": true, 10 10 "noImplicitAny": false, 11 11 "skipLibCheck": true, 12 + "moduleDetection": "auto", 12 13 "baseUrl": ".", 13 14 "paths": { 14 - "@src/*": ["src/*"] 15 + "@src/*": ["src/*"], 16 + "@bluesky/*": ["src/bluesky/*"], 17 + "@image/*": ["src/image/*"], 18 + "@logger/*": ["src/logger/*"], 19 + "@media/*": ["src/media/*"], 20 + "@video/*": ["src/video/*"] 15 21 }, 16 22 "outDir": "./dist", 17 23 "rootDir": "./src",