13个文件已修改
3个文件已删除
17个文件已添加
4 文件已重命名
| | |
| | | <selectionStates> |
| | | <SelectionState runConfigName="app"> |
| | | <option name="selectionMode" value="DROPDOWN" /> |
| | | <DropdownSelection timestamp="2024-11-27T11:37:15.668942400Z"> |
| | | <Target type="DEFAULT_BOOT"> |
| | | <handle> |
| | | <DeviceId pluginId="Default" identifier="serial=127.0.0.1:5555;connection=03e18c1a" /> |
| | | </handle> |
| | | </Target> |
| | | </DropdownSelection> |
| | | <DialogSelection /> |
| | | </SelectionState> |
| | | <SelectionState runConfigName="SessionProfileActivity"> |
| | | <option name="selectionMode" value="DROPDOWN" /> |
| | | </SelectionState> |
| | | </selectionStates> |
| | | </component> |
| | |
| | | <option name="codename" value="q6q" /> |
| | | <option name="id" value="q6q" /> |
| | | <option name="manufacturer" value="Samsung" /> |
| | | <option name="name" value="SM-F956B" /> |
| | | <option name="name" value="Galaxy Z Fold6" /> |
| | | <option name="screenDensity" value="420" /> |
| | | <option name="screenX" value="1856" /> |
| | | <option name="screenY" value="2160" /> |
| | |
| | | implementation(libs.androidx.ui.tooling.preview) |
| | | implementation(libs.androidx.material3) |
| | | implementation(libs.material) |
| | | implementation(libs.firebase.storage.ktx) |
| | | testImplementation(libs.junit) |
| | | androidTestImplementation(libs.androidx.junit) |
| | | androidTestImplementation(libs.androidx.espresso.core) |
| | |
| | | implementation(libs.okhttp) |
| | | implementation(libs.appcompat) |
| | | implementation(libs.constraintlayout) |
| | | |
| | | |
| | | } |
| | |
| | | <category android:name="android.intent.category.LAUNCHER" /> |
| | | </intent-filter> |
| | | </activity> |
| | | |
| | | <activity android:name=".SessionProfileActivity" |
| | | android:windowSoftInputMode="adjustResize" /> |
| | | |
| | | <activity android:name=".UploadSessionActivity" |
| | | android:windowSoftInputMode="adjustResize" /> |
| | | |
| | | </application> |
| | | |
| | | </manifest> |
| | |
| | | package com.shsening.autouploadvideos |
| | | |
| | | |
| | | data class PlatformItem( |
| | | val id: Int, |
| | | val name: String, |
| | | val enabled: Boolean, |
| | | var url: String |
| | | ) |
| | | class AppConfig { |
| | | companion object { |
| | | const val APP_VERSION_NAME = "1.1.0" |
| | |
| | | const val URL_UPLOAD_SERVER_YOUTUBE = "https://creator.douyin.com/creator-micro/content/publish?enter_from=publish_page" |
| | | const val URL_UPLOAD_SERVER_TIKTOK = "https://creator.douyin.com/creator-micro/content/publish?enter_from=publish_page" |
| | | |
| | | val SESSION_PROFILE_PALTFORM_LISTS = listOf( |
| | | PlatformItem(1,"抖音",true,"https://creator.douyin.com/creator-micro/content/publish?enter_from=publish_page"), |
| | | PlatformItem(2,"快手",true,"https://cp.kuaishou.com/article/publish/video"), |
| | | PlatformItem(3,"视频号",true,"https://channels.weixin.qq.com/platform/post/create"), |
| | | PlatformItem(4,"小红书",false,"https://creator.douyin.com/creator-micro/content/publish?enter_from=publish_page"), |
| | | PlatformItem(5,"YouTube", false,"https://creator.douyin.com/creator-micro/content/publish?enter_from=publish_page"), |
| | | PlatformItem(6,"TikTok", false,"https://creator.douyin.com/creator-micro/content/publish?enter_from=publish_page") |
| | | ) |
| | | val SESSION_PROFILE_TYPE: Map<Int, String> = mapOf(0 to "美女跳舞", 1 to "美女穿搭",2 to "AI美景", 3 to "搞笑动物") |
| | | |
| | | } |
| | | } |
| New file |
| | |
| | | import android.content.Context |
| | | import android.webkit.CookieManager |
| | | import com.shsening.autouploadvideos.AppConfig |
| | | import org.json.JSONObject |
| | | |
| | | object CookieHelper { |
| | | private const val PREF_NAME = "CookieHelper" |
| | | |
| | | fun saveSessionProfileUrlCookies(context: Context, sessionProfileId: String, url: String, cookies: String) { |
| | | val sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) |
| | | val sessionCookiesMap = sharedPreferences.getString(sessionProfileId, "{}") |
| | | val sessionCookies = JSONObject(sessionCookiesMap!!) |
| | | sessionCookies.put(url, cookies) |
| | | sharedPreferences.edit().putString(sessionProfileId, sessionCookies.toString()).apply() |
| | | } |
| | | |
| | | fun getAllSessionProfileCookies(context: Context, sessionProfileId: String): Map<String, String> { |
| | | val sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) |
| | | val sessionCookiesMap = sharedPreferences.getString(sessionProfileId, "{}") |
| | | val sessionCookies = JSONObject(sessionCookiesMap!!) |
| | | return sessionCookies.keys().asSequence().associateWith { sessionCookies.getString(it) } |
| | | } |
| | | |
| | | fun removeCookiesForUrl(url: String) { |
| | | val cookieManager = CookieManager.getInstance() |
| | | val cookies = cookieManager.getCookie(url) // 获取特定 URL 的所有 Cookie |
| | | if (cookies != null) { |
| | | val cookieArray = cookies.split(";") // 按分号分隔多个 Cookie |
| | | for (cookie in cookieArray) { |
| | | val cookieParts = cookie.split("=") |
| | | if (cookieParts.size > 0) { |
| | | val cookieName = cookieParts[0].trim() |
| | | // 设置一个过期的 Cookie 覆盖原来的 |
| | | cookieManager.setCookie(url, "$cookieName=; Expires=Thu, 01 Jan 1970 00:00:00 GMT") |
| | | } |
| | | } |
| | | // 确保将更改同步到存储中 |
| | | cookieManager.flush() |
| | | } |
| | | } |
| | | |
| | | fun removeCookieForAllPlatform(){ |
| | | AppConfig.SESSION_PROFILE_PALTFORM_LISTS.forEach { item -> |
| | | removeCookiesForUrl(item.url) |
| | | } |
| | | } |
| | | |
| | | fun saveSessionCookies(context: Context,sessionProfileId:String) { |
| | | var cookieManager = CookieManager.getInstance() |
| | | AppConfig.SESSION_PROFILE_PALTFORM_LISTS.forEach { item -> |
| | | val cookies = cookieManager.getCookie(item.url) |
| | | if (cookies != null) { |
| | | saveSessionProfileUrlCookies( |
| | | context, |
| | | sessionProfileId, |
| | | item.url, |
| | | cookies |
| | | ) |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | package com.shsening.autouploadvideos |
| | | |
| | | import android.content.ClipData |
| | | import android.content.ClipboardManager |
| | | import android.content.Context |
| | | import android.os.Bundle |
| | | import android.view.LayoutInflater |
| | | import android.view.View |
| | | import android.view.ViewGroup |
| | | import android.webkit.WebView |
| | | import android.widget.ImageView |
| | | import android.widget.TextView |
| | | import android.widget.Toast |
| | | import androidx.fragment.app.Fragment |
| | | |
| | | |
| | | class HelpFragment : Fragment() { |
| | | |
| | |
| | | savedInstanceState: Bundle? |
| | | ): View? { |
| | | val view = inflater.inflate(R.layout.fragment_help, container, false) |
| | | |
| | | val textView = view.findViewById<TextView>(R.id.androidIdText); |
| | | textView.text = "Android ID: ${AppConfig.CLIENT_ANDROID_ID }" |
| | | textView.setOnClickListener { |
| | | val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager |
| | | val clip = ClipData.newPlainText("label", textView.text) |
| | | clipboard.setPrimaryClip(clip) |
| | | Toast.makeText(requireContext(), AppConfig.MSG_MY_TAB_COPY_TO_CLIPBOARD, Toast.LENGTH_SHORT).show() |
| | | } |
| | | |
| | | // 加载图片 |
| | | val imageView = view.findViewById<ImageView>(R.id.imageView) |
| | | imageView.setImageResource(R.drawable.qrcode) // 请替换为你的图片 |
| | | |
| | | // 设置简介 |
| | | view.findViewById<TextView>(R.id.descriptionText).text = AppConfig.MSG_MY_TAB_ADD_WECHAT |
| | | |
| | | webView = view.findViewById(R.id.webView) |
| | | webView.loadUrl("file:///android_asset/help/help.html") |
| | | |
| | | return view |
| | | } |
| | | } |
| | |
| | | |
| | | import android.Manifest |
| | | import android.app.AlertDialog |
| | | import android.content.ActivityNotFoundException |
| | | import android.content.Context |
| | | import android.content.Intent |
| | | import android.content.pm.PackageManager |
| | | import android.os.Bundle |
| | | import android.view.View |
| | | import android.widget.Button |
| | | import androidx.appcompat.app.AppCompatActivity |
| | | import android.graphics.Color |
| | | import android.net.Uri |
| | | import android.provider.Settings |
| | | import android.util.Log |
| | | import android.webkit.WebView |
| | | import android.widget.TextView |
| | | import android.widget.Toast |
| | | import androidx.core.content.FileProvider |
| | | import androidx.fragment.app.Fragment |
| | | import androidx.fragment.app.FragmentManager |
| | | import com.shsening.autouploadvideos.utils.Utils |
| | | import kotlinx.coroutines.CoroutineScope |
| | | import kotlinx.coroutines.Dispatchers |
| | | import kotlinx.coroutines.launch |
| | | import kotlinx.coroutines.withContext |
| | | import okhttp3.Call |
| | | import okhttp3.Callback |
| | | import okhttp3.OkHttpClient |
| | | import okhttp3.Request |
| | | import okhttp3.Response |
| | | import org.json.JSONArray |
| | | import org.json.JSONObject |
| | | import java.io.File |
| | | import java.io.IOException |
| | | import kotlinx.coroutines.* |
| | | import okhttp3.internal.wait |
| | | import java.io.FileOutputStream |
| | | import java.util.concurrent.CountDownLatch |
| | | |
| | | |
| | | class MainActivity : AppCompatActivity() { |
| | |
| | | fragmentManager = supportFragmentManager |
| | | |
| | | // 默认显示第一个 Fragment |
| | | /* |
| | | setCurrentFragment(UploadFragment()) |
| | | |
| | | findViewById<Button>(R.id.tabUpload).setOnClickListener { |
| | | setCurrentFragment(UploadFragment()) |
| | | highlightTab(it) |
| | | } |
| | | */ |
| | | setCurrentFragment(UploadListFragment()) |
| | | findViewById<Button>(R.id.tabUpload).setOnClickListener { |
| | | setCurrentFragment(UploadListFragment()) |
| | | highlightTab(it) |
| | | } |
| | | |
| | | findViewById<Button>(R.id.tabHelp).setOnClickListener { |
| | | setCurrentFragment(HelpFragment()) |
| | | findViewById<Button>(R.id.tabSetting).setOnClickListener { |
| | | setCurrentFragment(SessionProfileListFragment()) |
| | | highlightTab(it) |
| | | } |
| | | |
| | | findViewById<Button>(R.id.tabMy).setOnClickListener { |
| | | setCurrentFragment(MyFragment()) |
| | | setCurrentFragment(HelpFragment()) |
| | | highlightTab(it) |
| | | } |
| | | CoroutineScope(Dispatchers.IO).launch { |
| | |
| | | private fun highlightTab(selectedButton: View) { |
| | | val tabButtons = listOf( |
| | | findViewById<Button>(R.id.tabUpload), |
| | | findViewById<Button>(R.id.tabHelp), |
| | | findViewById<Button>(R.id.tabSetting), |
| | | findViewById<Button>(R.id.tabMy) |
| | | ) |
| | | |
| New file |
| | |
| | | package com.shsening.autouploadvideos |
| | | |
| | | import CookieHelper |
| | | import CookieHelper.saveSessionProfileUrlCookies |
| | | import Profile |
| | | import SessionProfile |
| | | import SessionProfileManager |
| | | import android.app.Activity |
| | | import android.content.Intent |
| | | import android.os.Bundle |
| | | import android.util.Log |
| | | import android.view.View |
| | | import android.webkit.CookieManager |
| | | import android.webkit.WebChromeClient |
| | | import android.webkit.WebView |
| | | import android.webkit.WebViewClient |
| | | import android.widget.ArrayAdapter |
| | | import android.widget.EditText |
| | | import android.widget.LinearLayout |
| | | import android.widget.ProgressBar |
| | | import android.widget.Spinner |
| | | import android.widget.Switch |
| | | import android.widget.TextView |
| | | import android.widget.Toast |
| | | import androidx.appcompat.app.AppCompatActivity |
| | | import java.util.UUID |
| | | |
| | | |
| | | class SessionProfileActivity : AppCompatActivity() { |
| | | |
| | | private lateinit var sessionProfileManager: SessionProfileManager |
| | | private lateinit var sessionProfile: SessionProfile |
| | | private val appConfigItems = AppConfig.SESSION_PROFILE_PALTFORM_LISTS // 获取平台信息列表 |
| | | private lateinit var editName: EditText |
| | | private lateinit var spinnerType: Spinner |
| | | private lateinit var switchContainer: LinearLayout |
| | | private lateinit var sessionProfileWebView:SessionProfileWebView |
| | | |
| | | private var isNewSessionProfile:Boolean = false; |
| | | |
| | | private lateinit var progressBar: ProgressBar |
| | | |
| | | private lateinit var activityReference: SessionProfileActivity |
| | | |
| | | override fun onCreate(savedInstanceState: Bundle?) { |
| | | super.onCreate(savedInstanceState) |
| | | setContentView(R.layout.activity_session_profile) |
| | | activityReference = this |
| | | |
| | | // 获取传入的 SessionProfile |
| | | sessionProfile = intent.getSerializableExtra("sessionProfile") as? SessionProfile |
| | | ?: SessionProfile( |
| | | id = "", |
| | | name = "", |
| | | type = 0, |
| | | lastUploadTime = "", |
| | | profiles = mutableListOf() |
| | | ) |
| | | |
| | | if (sessionProfile.id == ""){ |
| | | isNewSessionProfile = true |
| | | sessionProfile.id = UUID.randomUUID().toString().replace("-", "") |
| | | findViewById<TextView>(R.id.txt_session_id).text = "new:"+sessionProfile.id |
| | | }else { |
| | | findViewById<TextView>(R.id.txt_session_id).text = sessionProfile.id |
| | | } |
| | | |
| | | editName = findViewById(R.id.edit_name) |
| | | findViewById<TextView>(R.id.edit_name).text = sessionProfile.name |
| | | |
| | | spinnerType = findViewById(R.id.spinner_type) |
| | | spinnerType.adapter = ArrayAdapter( |
| | | this, |
| | | android.R.layout.simple_spinner_item, |
| | | AppConfig.SESSION_PROFILE_TYPE.values.toList() |
| | | ) |
| | | spinnerType.setSelection(sessionProfile.type) |
| | | |
| | | switchContainer = findViewById(R.id.switch_container) |
| | | setupSwitches() |
| | | |
| | | // 初始化 SessionProfileManager |
| | | sessionProfileManager = SessionProfileManager(this) |
| | | |
| | | progressBar = findViewById(R.id.progressBar) |
| | | sessionProfileWebView = findViewById<SessionProfileWebView>(R.id.sessionProfileWebView) |
| | | sessionProfileWebView.setInitialScale(75) // 初始缩放比例 |
| | | sessionProfileWebView.isFocusableInTouchMode = true |
| | | try{ |
| | | Log.e("shsening", sessionProfile.id) |
| | | |
| | | Log.e("shsening", CookieManager.getInstance().getCookie(AppConfig.URL_UPLOAD_SERVER_DOUYIN)) |
| | | |
| | | }catch(e:Exception){ |
| | | Log.e("shsening", "there is no cookie founded") |
| | | |
| | | } |
| | | sessionProfileWebView.loadSessionProfileCookies(sessionProfile.id) |
| | | try{ |
| | | Log.e("shsening", CookieManager.getInstance().getCookie(AppConfig.URL_UPLOAD_SERVER_DOUYIN)) |
| | | |
| | | }catch(e:Exception){ |
| | | Log.e("shsening", "there is no cookie founded") |
| | | |
| | | } |
| | | sessionProfileWebView.webViewClient = object : WebViewClient() { |
| | | |
| | | override fun onPageStarted( |
| | | view: WebView?, |
| | | url: String?, |
| | | favicon: android.graphics.Bitmap? |
| | | ) { |
| | | super.onPageStarted(view, url, favicon) |
| | | progressBar.visibility = View.VISIBLE |
| | | } |
| | | override fun onPageFinished(view: WebView?, url: String?) { |
| | | super.onPageFinished(view, url) |
| | | progressBar.visibility = View.GONE |
| | | url?.let{ |
| | | Log.e("shsening", url) |
| | | try { |
| | | val cookies = CookieManager.getInstance().getCookie(url) |
| | | if (cookies != null) { |
| | | CookieHelper.saveSessionProfileUrlCookies( |
| | | activityReference, |
| | | sessionProfile.id, |
| | | url, |
| | | cookies |
| | | ) |
| | | } else { |
| | | Log.w("shsening", "No cookies found for URL: $url") |
| | | } |
| | | } catch (e: java.lang.Exception) { |
| | | Log.e("CookieHelper", "Failed to get cookies: ", e) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | sessionProfileWebView.webChromeClient = object : WebChromeClient() { |
| | | override fun onShowCustomView(view: View?, callback: CustomViewCallback?) { |
| | | super.onShowCustomView(view, callback) |
| | | view?.requestFocus() |
| | | } |
| | | } |
| | | // 保存按钮的监听 |
| | | findViewById<TextView>(R.id.btn_save).setOnClickListener { |
| | | saveSessionProfile() |
| | | } |
| | | findViewById<TextView>(R.id.btn_back).setOnClickListener { |
| | | finish() |
| | | } |
| | | } |
| | | |
| | | |
| | | private fun setupSwitches() { |
| | | switchContainer.removeAllViews() |
| | | |
| | | // 创建一个水平的 LinearLayout |
| | | var horizontalLayout: LinearLayout? = null |
| | | |
| | | // 遍历从 AppConfig 获取的所有平台项目 |
| | | for ((index, platform) in appConfigItems.withIndex()) { |
| | | if (index % 2 == 0) { |
| | | // 每两项创建一个新的水平布局 |
| | | horizontalLayout = LinearLayout(this).apply { |
| | | orientation = LinearLayout.HORIZONTAL |
| | | layoutParams = LinearLayout.LayoutParams( |
| | | LinearLayout.LayoutParams.MATCH_PARENT, |
| | | LinearLayout.LayoutParams.WRAP_CONTENT |
| | | ) |
| | | } |
| | | } |
| | | |
| | | val switchView:Switch = Switch(this).apply { |
| | | text = platform.name |
| | | val existingProfile = sessionProfile.profiles.find { it.name == platform.name } |
| | | isChecked = existingProfile?.status ?: false |
| | | isEnabled = platform.enabled |
| | | |
| | | // 开关监听器 |
| | | setOnCheckedChangeListener { _, isChecked -> |
| | | if (existingProfile != null) { |
| | | existingProfile.status = isChecked |
| | | } else { |
| | | sessionProfile.addOrUpdateProfile(Profile(name = platform.name, status = isChecked)) |
| | | } |
| | | if (isChecked) { |
| | | //sessionProfileWebView.loadSessionProfileCookies(sessionProfile.id) |
| | | sessionProfileWebView.loadUrl(platform.url) |
| | | } |
| | | } |
| | | layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply { |
| | | marginStart = 8 |
| | | marginEnd = 8 |
| | | } |
| | | } |
| | | |
| | | // 添加 Switch 到水平布局 |
| | | horizontalLayout?.addView(switchView) |
| | | |
| | | // 如果是偶数索引或者最后一个项目,添加水平布局到主容器 |
| | | if (index % 2 == 1 || index == appConfigItems.size - 1) { |
| | | switchContainer.addView(horizontalLayout) |
| | | } |
| | | } |
| | | } |
| | | |
| | | private fun saveSessionProfile() { |
| | | try { |
| | | CookieHelper.saveSessionCookies(this,sessionProfile.id) |
| | | val name = editName.text.toString().trim() // 获取输入的名称 |
| | | if (name.isEmpty()) { |
| | | Toast.makeText(this, "Name cannot be empty!", Toast.LENGTH_SHORT).show() |
| | | return |
| | | } |
| | | if (isNewSessionProfile and sessionProfileManager.checkProfileNameExist(name)){ |
| | | Toast.makeText(this, "Name already exist !", Toast.LENGTH_SHORT).show() |
| | | return // 不保存 |
| | | } |
| | | sessionProfile.name = name |
| | | sessionProfile.type = spinnerType.selectedItemPosition |
| | | sessionProfileManager.addOrUpdateSessionProfile(sessionProfile) |
| | | sessionProfileManager.saveToFile() |
| | | |
| | | Toast.makeText(this, "Session profile saved", Toast.LENGTH_SHORT).show() |
| | | setResult(Activity.RESULT_OK, Intent()) |
| | | finish() |
| | | }catch (e:Exception){ |
| | | e.printStackTrace() |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | package com.shsening.autouploadvideos |
| | | |
| | | import SessionProfile |
| | | import SessionProfileManager |
| | | import android.app.Activity |
| | | import android.app.AlertDialog |
| | | import android.content.Intent |
| | | import android.os.Bundle |
| | | import android.view.LayoutInflater |
| | | import android.view.View |
| | | import android.view.ViewGroup |
| | | import android.widget.AdapterView |
| | | import android.widget.ArrayAdapter |
| | | import android.widget.Button |
| | | import android.widget.ListView |
| | | import androidx.fragment.app.Fragment |
| | | import android.widget.TextView |
| | | import com.shsening.autouploadvideos.utils.Utils |
| | | import java.io.Serializable |
| | | |
| | | class SessionProfileListFragment : Fragment() { |
| | | |
| | | private lateinit var listView: ListView |
| | | private lateinit var btnNew: View |
| | | private val REQUEST_CODE = 100 |
| | | |
| | | private val sessionProfiles = mutableListOf<SessionProfile>() // Replace with your real data source |
| | | |
| | | override fun onCreateView( |
| | | inflater: LayoutInflater, container: ViewGroup?, |
| | | savedInstanceState: Bundle? |
| | | ): View { |
| | | val view = inflater.inflate(R.layout.fragment_session_profile_list, container, false) |
| | | |
| | | listView = view.findViewById(R.id.list_view) |
| | | btnNew = view.findViewById(R.id.btn_new) |
| | | |
| | | setupListView() |
| | | setupNewButton() |
| | | |
| | | return view |
| | | } |
| | | |
| | | private fun setupListView() { |
| | | // 清空适配器 |
| | | listView.adapter = null |
| | | |
| | | // 从 SessionProfileManager 获取所有的 SessionProfile |
| | | val sessionProfiles = SessionProfileManager(requireContext()).getAllProfiles() |
| | | |
| | | // 自定义适配器实现删除按钮功能 |
| | | val adapter = object : ArrayAdapter<SessionProfile>( |
| | | requireContext(), |
| | | android.R.layout.simple_list_item_2, |
| | | sessionProfiles |
| | | ) { |
| | | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { |
| | | val inflater = LayoutInflater.from(context) |
| | | val view = convertView ?: inflater.inflate(R.layout.list_item_with_delete, parent, false) |
| | | |
| | | // 获取当前的 SessionProfile |
| | | val sessionProfile = getItem(position) |
| | | |
| | | // 填充数据 |
| | | val text1 = view.findViewById<TextView>(android.R.id.text1) |
| | | val textType = Utils.getSessionProfileValueByPosition(sessionProfile?.type) |
| | | text1.text = "${sessionProfile?.name} (${textType})" |
| | | |
| | | val text2 = view.findViewById<TextView>(android.R.id.text2) |
| | | text2.text = sessionProfile?.lastUploadTime |
| | | |
| | | // 设置删除按钮 |
| | | val deleteButton = view.findViewById<Button>(R.id.deleteButton) |
| | | deleteButton.setOnClickListener { |
| | | // 弹出确认对话框 |
| | | AlertDialog.Builder(context) |
| | | .setTitle("删除确认") |
| | | .setMessage("确定要删除 ${sessionProfile?.name} 吗?") |
| | | .setPositiveButton("确认") { _, _ -> |
| | | // 从管理器中删除 |
| | | sessionProfile?.let { |
| | | SessionProfileManager(requireContext()).removeProfile(it.id) |
| | | } |
| | | // 刷新列表 |
| | | setupListView() |
| | | } |
| | | .setNegativeButton("取消", null) |
| | | .show() |
| | | } |
| | | |
| | | // 设置行点击事件 |
| | | view.setOnClickListener { |
| | | val intent = Intent(requireContext(), SessionProfileActivity::class.java) |
| | | intent.putExtra("sessionProfile", sessionProfile as Serializable) // 传递选中的 Profile 对象 |
| | | startActivityForResult(intent, REQUEST_CODE) |
| | | } |
| | | |
| | | return view |
| | | } |
| | | } |
| | | |
| | | // 设置适配器到 ListView |
| | | listView.adapter = adapter |
| | | |
| | | // 设置点击事件 |
| | | listView.onItemClickListener = |
| | | AdapterView.OnItemClickListener { _, _, position, _ -> |
| | | val selectedProfile = sessionProfiles[position] |
| | | val intent = Intent(requireContext(), SessionProfileActivity::class.java) |
| | | intent.putExtra("sessionProfile", selectedProfile as Serializable) // 传递选中的 Profile 对象 |
| | | startActivityForResult(intent, REQUEST_CODE) |
| | | } |
| | | } |
| | | |
| | | // 接收到 Activity 返回结果时刷新 Fragment |
| | | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
| | | super.onActivityResult(requestCode, resultCode, data) |
| | | if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) { |
| | | // 刷新 Fragment 内容 |
| | | setupListView() |
| | | } |
| | | } |
| | | |
| | | private fun setupNewButton() { |
| | | btnNew.setOnClickListener { |
| | | val intent = Intent(requireContext(), SessionProfileActivity::class.java) |
| | | startActivityForResult(intent, REQUEST_CODE) |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | import android.content.Context |
| | | import android.os.Parcel |
| | | import com.shsening.autouploadvideos.AppConfig |
| | | import org.json.JSONArray |
| | | import org.json.JSONObject |
| | | import java.io.File |
| | | import java.io.Serializable |
| | | import android.os.Parcelable |
| | | import com.shsening.autouploadvideos.utils.Utils |
| | | |
| | | data class SessionProfile( |
| | | var id: String, |
| | | var name: String, |
| | | var type: Int, |
| | | var lastUploadTime: String, |
| | | val profiles: MutableList<Profile> = mutableListOf() |
| | | ) : Parcelable, Serializable { // 逗号分隔接口 |
| | | // Parcelable 的实现 |
| | | override fun writeToParcel(parcel: Parcel, flags: Int) { |
| | | parcel.writeString(id) |
| | | parcel.writeString(name) |
| | | parcel.writeInt(type) |
| | | parcel.writeString(lastUploadTime) |
| | | parcel.writeTypedList(profiles) // 对于列表,使用 writeTypedList |
| | | } |
| | | override fun describeContents(): Int { |
| | | return 0 |
| | | } |
| | | // Parcelable 的 Companion Object |
| | | companion object CREATOR : Parcelable.Creator<SessionProfile> { |
| | | override fun createFromParcel(parcel: Parcel): SessionProfile { |
| | | val id = parcel.readString().orEmpty() |
| | | val name = parcel.readString().orEmpty() |
| | | val type = parcel.readInt() |
| | | val lastUploadTime = parcel.readString().orEmpty() |
| | | val profiles = mutableListOf<Profile>().apply { |
| | | parcel.readTypedList(this, Profile.CREATOR) |
| | | } |
| | | return SessionProfile(id, name, type, lastUploadTime, profiles) |
| | | } |
| | | |
| | | override fun newArray(size: Int): Array<SessionProfile?> { |
| | | return arrayOfNulls(size) |
| | | } |
| | | } |
| | | // 添加或更新 Profile 的方法 |
| | | fun addOrUpdateProfile(profile: Profile) { |
| | | val existingProfile = profiles.find { it.name == profile.name } |
| | | if (existingProfile != null) { |
| | | existingProfile.status = profile.status |
| | | } else { |
| | | profiles.add(profile) |
| | | } |
| | | } |
| | | fun getValidProfileNames(): List<String> { |
| | | return profiles.filter { it.status }.map { it.name } |
| | | } |
| | | } |
| | | |
| | | data class Profile( |
| | | var name: String, |
| | | var status: Boolean |
| | | ) : Serializable,Parcelable { |
| | | |
| | | override fun writeToParcel(parcel: Parcel, flags: Int) { |
| | | parcel.writeString(name) |
| | | parcel.writeByte(if (status) 1 else 0) |
| | | } |
| | | override fun describeContents(): Int { |
| | | return 0 |
| | | } |
| | | companion object CREATOR : Parcelable.Creator<Profile> { |
| | | override fun createFromParcel(parcel: Parcel): Profile { |
| | | val name = parcel.readString().orEmpty() |
| | | val status = parcel.readByte() != 0.toByte() |
| | | return Profile(name, status) |
| | | } |
| | | override fun newArray(size: Int): Array<Profile?> { |
| | | return arrayOfNulls(size) |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * val profileManager = SessionProfileManager(context) |
| | | * profileManager.addProfile(1, "douyin", true) |
| | | * val profiles = profileManager.getProfile(1) |
| | | * profileManager.removeProfile(1) |
| | | * val allProfiles = profileManager.getAllProfiles() |
| | | */ |
| | | |
| | | class SessionProfileManager(private val context: Context) { |
| | | companion object { |
| | | private const val FILE_NAME = "session_profiles.json" |
| | | } |
| | | |
| | | private val sessionProfiles = mutableListOf<SessionProfile>() |
| | | |
| | | init { |
| | | ensureFileExists() // 确保文件存在 |
| | | loadProfiles() |
| | | } |
| | | // 确保文件存在 |
| | | private fun ensureFileExists() { |
| | | val file = File(context.filesDir, FILE_NAME) |
| | | if (!file.exists()) { |
| | | file.writeText("[]") // 创建空 JSON 数组作为初始内容 |
| | | } |
| | | } |
| | | |
| | | // 加载文件内容 |
| | | private fun loadProfiles() { |
| | | val file = File(context.filesDir, FILE_NAME) |
| | | val jsonString = file.readText() |
| | | val jsonArray = JSONArray(jsonString) |
| | | sessionProfiles.clear() |
| | | for (i in 0 until jsonArray.length()) { |
| | | val jsonObj = jsonArray.getJSONObject(i) |
| | | sessionProfiles.add(jsonToSessionProfile(jsonObj)) |
| | | } |
| | | } |
| | | |
| | | // 保存到文件 |
| | | private fun saveProfiles() { |
| | | val jsonArray = JSONArray() |
| | | sessionProfiles.forEach { profile -> |
| | | jsonArray.put(sessionProfileToJson(profile)) |
| | | } |
| | | val file = File(context.filesDir, FILE_NAME) |
| | | file.writeText(jsonArray.toString()) |
| | | } |
| | | |
| | | fun saveToFile() { |
| | | saveProfiles() |
| | | } |
| | | |
| | | // Convert JSON to SessionProfile |
| | | private fun jsonToSessionProfile(json: JSONObject): SessionProfile { |
| | | val id = json.getString("id") |
| | | val name = json.getString("name") |
| | | val type = json.getInt("type") |
| | | val lastUploadTime = json.getString("lastUploadTime") |
| | | val profilesJsonArray = json.getJSONArray("profiles") |
| | | val profiles = mutableListOf<Profile>() |
| | | for (i in 0 until profilesJsonArray.length()) { |
| | | val profileJson = profilesJsonArray.getJSONObject(i) |
| | | profiles.add(Profile(profileJson.getString("name"), profileJson.getBoolean("status"))) |
| | | } |
| | | return SessionProfile(id, name, type, lastUploadTime, profiles) |
| | | } |
| | | |
| | | // Convert SessionProfile to JSON |
| | | private fun sessionProfileToJson(profile: SessionProfile): JSONObject { |
| | | val json = JSONObject() |
| | | json.put("id", profile.id) |
| | | json.put("name", profile.name) |
| | | json.put("type", profile.type) |
| | | json.put("lastUploadTime", profile.lastUploadTime) |
| | | val profilesJsonArray = JSONArray() |
| | | profile.profiles.forEach { |
| | | val profileJson = JSONObject() |
| | | profileJson.put("name", it.name) |
| | | profileJson.put("status", it.status) |
| | | profilesJsonArray.put(profileJson) |
| | | } |
| | | json.put("profiles", profilesJsonArray) |
| | | return json |
| | | } |
| | | |
| | | // Get all session profiles |
| | | fun getAllProfiles(): List<SessionProfile> { |
| | | if (sessionProfiles.isEmpty()) { |
| | | loadProfiles() |
| | | } |
| | | return sessionProfiles.toList() // 将内部的 profiles 返回为不可变列表 |
| | | } |
| | | // Add a new session profile |
| | | fun checkProfileNameExist(name: String): Boolean { |
| | | return sessionProfiles.any { it.name == name } |
| | | } |
| | | |
| | | // Remove a session profile |
| | | fun removeProfile(id: String) { |
| | | sessionProfiles.removeAll { it.id == id } |
| | | saveProfiles() |
| | | } |
| | | |
| | | // 3. 添加或更新 SessionProfile |
| | | fun addOrUpdateSessionProfile(sessionProfile: SessionProfile) { |
| | | val existingProfile = sessionProfiles.find { it.id == sessionProfile.id } |
| | | if (existingProfile != null) { |
| | | val index = sessionProfiles.indexOf(existingProfile) |
| | | sessionProfiles[index] = sessionProfile |
| | | } else { |
| | | // 添加新的 Profile |
| | | sessionProfiles.add(sessionProfile) |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | package com.shsening.autouploadvideos |
| | | |
| | | import CookieHelper |
| | | import android.content.Context |
| | | import android.os.Handler |
| | | import android.os.Looper |
| | | import android.text.TextUtils |
| | | import android.util.AttributeSet |
| | | import android.util.Log |
| | | import android.webkit.CookieManager |
| | | import android.webkit.CookieSyncManager |
| | | import android.webkit.WebSettings |
| | | import android.webkit.WebView |
| | | import android.widget.Toast |
| | | |
| | | |
| | | class SessionProfileWebView @JvmOverloads constructor( |
| | | context: Context, |
| | | attrs: AttributeSet? = null, |
| | | defStyle: Int = 0 |
| | | ) : WebView(context, attrs, defStyle) { |
| | | |
| | | init { |
| | | settings.javaScriptEnabled = true |
| | | settings.userAgentString = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" |
| | | settings.javaScriptEnabled = true |
| | | settings.domStorageEnabled = true |
| | | settings.databaseEnabled = true |
| | | settings.setSupportZoom(true) |
| | | settings.builtInZoomControls = true |
| | | settings.displayZoomControls = false |
| | | settings.allowFileAccess = true |
| | | settings.textZoom = 50 // Set zoom level to 30% |
| | | settings.useWideViewPort = true |
| | | settings.loadWithOverviewMode = true |
| | | settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.NORMAL |
| | | CookieManager.getInstance().setAcceptCookie(true) // 接受 Cookie |
| | | CookieManager.getInstance().setAcceptThirdPartyCookies(this, true) // 接受第三方 Cookie |
| | | settings.setCacheMode(WebSettings.LOAD_NO_CACHE) |
| | | } |
| | | |
| | | |
| | | // 加载 SessionProfile 的所有 URL-Cookie 映射到 WebView |
| | | fun loadSessionProfileCookies(sessionProfileId: String) { |
| | | //CookieHelper.removeCookieForAllPlatform() |
| | | CookieManager.getInstance().removeAllCookies(null) |
| | | CookieManager.getInstance().flush() |
| | | val sessionCookies = CookieHelper.getAllSessionProfileCookies(context, sessionProfileId) |
| | | for ((url, cookies) in sessionCookies) { |
| | | splitCookieStringAndSetCookie(url,cookies) |
| | | } |
| | | } |
| | | private fun splitCookieStringAndSetCookie(url:String,cookies:String){ |
| | | val cookie_part = cookies.split(";".toRegex()).dropLastWhile { it.isEmpty() } |
| | | .toTypedArray() |
| | | val cookieManager = CookieManager.getInstance() |
| | | if (cookie_part!=null) { |
| | | for (cook in cookie_part) { |
| | | //一定要一个一个set, 如果是拼接的话,设置不成功。 |
| | | //cookieManager.setCookie(url, cook.trim{ it <= ' ' }) |
| | | cookieManager.setCookie(url, cook.trim()) |
| | | } |
| | | } |
| | | cookieManager.flush() |
| | | } |
| | | |
| | | // 访问某个 URL 时,保存当前 SessionProfile 的 Cookie |
| | | fun saveCurrentSessionProfileUrlCookies(sessionProfileId:String, url: String) { |
| | | val cookieManager = CookieManager.getInstance() |
| | | val cookies = cookieManager.getCookie(url) |
| | | if (cookies != null) { |
| | | CookieHelper.saveSessionProfileUrlCookies(context, sessionProfileId, url, cookies) |
| | | } |
| | | } |
| | | |
| | | |
| | | override fun loadUrl(url: String) { |
| | | try{ |
| | | val headers = mapOf( |
| | | "pragma" to "no-cache", |
| | | "priority" to "u=0, i", |
| | | "sec-ch-ua" to "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"", |
| | | "sec-ch-ua-mobile" to "?0", |
| | | "sec-ch-ua-platform" to "\"Windows\"", |
| | | "sec-fetch-dest" to "document", |
| | | "sec-fetch-mode" to "navigate", |
| | | "sec-fetch-site" to "none", |
| | | "sec-fetch-user" to "?1", |
| | | "upgrade-insecure-requests" to "1" |
| | | ) |
| | | super.loadUrl(url,headers) |
| | | }catch (e:Exception){ |
| | | e.printStackTrace() |
| | | } |
| | | } |
| | | fun loadUrlNoCache(url: String) { |
| | | try{ |
| | | val headers = mapOf( |
| | | "pragma" to "no-cache", |
| | | "priority" to "u=0, i", |
| | | "sec-ch-ua" to "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"", |
| | | "sec-ch-ua-mobile" to "?0", |
| | | "sec-ch-ua-platform" to "\"Windows\"", |
| | | "sec-fetch-dest" to "document", |
| | | "sec-fetch-mode" to "navigate", |
| | | "sec-fetch-site" to "none", |
| | | "sec-fetch-user" to "?1", |
| | | "upgrade-insecure-requests" to "1" |
| | | ) |
| | | super.clearCache(true) |
| | | super.loadUrl(url,headers) |
| | | }catch (e:Exception){ |
| | | e.printStackTrace() |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | |
| | | import androidx.core.content.FileProvider |
| | | import androidx.fragment.app.Fragment |
| | | import androidx.lifecycle.lifecycleScope |
| | | import com.shsening.autouploadvideos.action.ActionDouyin |
| | | import com.shsening.autouploadvideos.action.ActionKuaishou |
| | | import com.shsening.autouploadvideos.action.ActionShipinhao |
| | | import com.shsening.autouploadvideos.utils.Utils |
| | | import kotlinx.coroutines.launch |
| | | import kotlinx.coroutines.runBlocking |
| | | import okhttp3.Call |
| | |
| | | |
| | | override fun onPageFinished(view: WebView?, url: String?) { |
| | | super.onPageFinished(view, url) |
| | | |
| | | view?.requestFocus() |
| | | |
| | | injectPageLoadFinishedActions() |
| | | |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | try { |
| | | |
| | | /* |
| | | |
| | | when (currentUploadServerType) { |
| | | AppConfig.UPLOADSERVERTYPE_DOUYIN -> { |
| | | val actionDouyin = |
| | |
| | | |
| | | } |
| | | } |
| | | |
| | | */ |
| | | }catch (e:Exception){ |
| | | e.printStackTrace() |
| | | } |
| | |
| | | currentUploadServerIndex++ |
| | | }else{ |
| | | lifecycleScope.launch { |
| | | Utils.sendVideoUploadedMessage( |
| | | requireContext(), |
| | | currentUploadVideoInfo.taskId |
| | | ) |
| | | Utils.sendVideoUploadedMessage(currentUploadVideoInfo.taskId) { result, error -> |
| | | Toast.makeText(context,error.toString(),Toast.LENGTH_SHORT) |
| | | } |
| | | } |
| | | startUploadTask() |
| | | } |
| New file |
| | |
| | | package com.shsening.autouploadvideos |
| | | |
| | | import SessionProfile |
| | | import SessionProfileManager |
| | | import android.app.Activity |
| | | import android.app.AlertDialog |
| | | import android.content.Intent |
| | | import android.os.Bundle |
| | | import android.view.LayoutInflater |
| | | import android.view.View |
| | | import android.view.ViewGroup |
| | | import android.widget.ArrayAdapter |
| | | import android.widget.Button |
| | | import android.widget.CheckBox |
| | | import android.widget.ListView |
| | | import androidx.fragment.app.Fragment |
| | | import android.widget.TextView |
| | | import com.shsening.autouploadvideos.utils.Utils |
| | | |
| | | class UploadListFragment : Fragment() { |
| | | |
| | | private lateinit var listView: ListView |
| | | private lateinit var btnAllTask: View |
| | | private val REQUEST_CODE = 100 |
| | | |
| | | private val sessionProfiles = mutableListOf<SessionProfile>() // Replace with your real data source |
| | | |
| | | override fun onCreateView( |
| | | inflater: LayoutInflater, container: ViewGroup?, |
| | | savedInstanceState: Bundle? |
| | | ): View { |
| | | val view = inflater.inflate(R.layout.upload_list, container, false) |
| | | |
| | | listView = view.findViewById(R.id.upload_list_view) |
| | | btnAllTask = view.findViewById(R.id.btn_all_task) |
| | | |
| | | setupListView() |
| | | setupAllTaskButton() |
| | | |
| | | return view |
| | | } |
| | | |
| | | private fun setupListView() { |
| | | // 清空适配器 |
| | | listView.adapter = null |
| | | |
| | | // 从 SessionProfileManager 获取所有的 SessionProfile |
| | | val sessionProfiles = SessionProfileManager(requireContext()).getAllProfiles() |
| | | |
| | | // 自定义适配器实现删除按钮功能 |
| | | val adapter = object : ArrayAdapter<SessionProfile>( |
| | | requireContext(), |
| | | android.R.layout.simple_list_item_2, |
| | | sessionProfiles |
| | | ) { |
| | | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { |
| | | val inflater = LayoutInflater.from(context) |
| | | val view = convertView ?: inflater.inflate(R.layout.list_item_with_checkbox, parent, false) |
| | | |
| | | // 获取当前的 SessionProfile |
| | | val sessionProfile = getItem(position) |
| | | |
| | | // 填充数据 |
| | | val text1 = view.findViewById<TextView>(android.R.id.text1) |
| | | val textType = Utils.getSessionProfileValueByPosition(sessionProfile?.type) |
| | | text1.text = "${sessionProfile?.name} (${textType})" |
| | | |
| | | val text2 = view.findViewById<TextView>(android.R.id.text2) |
| | | text2.text = sessionProfile?.lastUploadTime |
| | | |
| | | // 设置删除按钮 |
| | | val deleteButton = view.findViewById<Button>(R.id.deleteButton) |
| | | deleteButton.setOnClickListener { |
| | | val intent = Intent(requireContext(), UploadSessionActivity::class.java) |
| | | val sessionProfiles = arrayListOf(sessionProfile) // 替换为实际的选定数组 |
| | | intent.putParcelableArrayListExtra("sessionProfiles", sessionProfiles) |
| | | startActivityForResult(intent, REQUEST_CODE) |
| | | } |
| | | |
| | | return view |
| | | } |
| | | } |
| | | |
| | | // 设置适配器到 ListView |
| | | listView.adapter = adapter |
| | | |
| | | } |
| | | fun getSelectedSessionProfiles(): ArrayList<SessionProfile> { |
| | | val selectedProfiles = ArrayList<SessionProfile>() |
| | | |
| | | // 获取 ListView 的适配器 |
| | | val adapter = listView.adapter as? ArrayAdapter<*> |
| | | // 确保适配器正确 |
| | | if (adapter != null) { |
| | | // 遍历 ListView 中的所有项 |
| | | for (i in 0 until adapter.count) { |
| | | // 获取当前的视图 |
| | | val view = listView.getChildAt(i) |
| | | // 获取 CheckBox 组件 |
| | | val checkBox = view?.findViewById<CheckBox>(R.id.itemCheckBox) |
| | | |
| | | // 检查 CheckBox 是否被选中 |
| | | if (checkBox?.isChecked == true) { |
| | | // 获取对应的 SessionProfile 对象 |
| | | val sessionProfile = adapter.getItem(i) as? SessionProfile |
| | | // 将选中的 SessionProfile 添加到结果列表中 |
| | | sessionProfile?.let { |
| | | selectedProfiles.add(it) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return selectedProfiles |
| | | } |
| | | |
| | | |
| | | // 接收到 Activity 返回结果时刷新 Fragment |
| | | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { |
| | | super.onActivityResult(requestCode, resultCode, data) |
| | | if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) { |
| | | // 刷新 Fragment 内容 |
| | | setupListView() |
| | | } |
| | | } |
| | | |
| | | private fun setupAllTaskButton() { |
| | | btnAllTask.setOnClickListener { |
| | | |
| | | var selected = getSelectedSessionProfiles() |
| | | if (selected.isEmpty()){ |
| | | AlertDialog.Builder(requireContext()) |
| | | .setTitle("提示") |
| | | .setMessage("请至少选择一个项目!") |
| | | .setPositiveButton("确定", null) |
| | | .show() |
| | | }else { |
| | | val intent = Intent(requireContext(), UploadSessionActivity::class.java) |
| | | intent.putParcelableArrayListExtra("sessionProfiles", selected) |
| | | startActivityForResult(intent, REQUEST_CODE) |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | package com.shsening.autouploadvideos |
| | | |
| | | import CookieHelper |
| | | import Profile |
| | | import SessionProfile |
| | | import SessionProfileManager |
| | | import android.app.Activity |
| | | import android.content.Intent |
| | | import android.net.Uri |
| | | import android.os.Bundle |
| | | import android.os.Handler |
| | | import android.os.Looper |
| | | import android.util.Log |
| | | import android.view.View |
| | | import android.webkit.CookieManager |
| | | import android.webkit.ValueCallback |
| | | import android.webkit.WebChromeClient |
| | | import android.webkit.WebView |
| | | import android.webkit.WebViewClient |
| | | import android.widget.ArrayAdapter |
| | | import android.widget.Button |
| | | import android.widget.EditText |
| | | import android.widget.LinearLayout |
| | | import android.widget.ProgressBar |
| | | import android.widget.Spinner |
| | | import android.widget.Switch |
| | | import android.widget.TextView |
| | | import android.widget.Toast |
| | | import androidx.appcompat.app.AppCompatActivity |
| | | import androidx.core.content.FileProvider |
| | | import com.shsening.autouploadvideos.AppConfig.Companion.SESSION_PROFILE_PALTFORM_LISTS |
| | | import com.shsening.autouploadvideos.action.ActionDouyin |
| | | import com.shsening.autouploadvideos.action.ActionKuaishou |
| | | import com.shsening.autouploadvideos.action.ActionShipinhao |
| | | import com.shsening.autouploadvideos.utils.DownloadHelper |
| | | import com.shsening.autouploadvideos.utils.DownloadReceiver |
| | | import com.shsening.autouploadvideos.utils.Utils |
| | | import kotlinx.coroutines.CoroutineScope |
| | | import kotlinx.coroutines.Dispatchers |
| | | import kotlinx.coroutines.launch |
| | | import okhttp3.Call |
| | | import okhttp3.Callback |
| | | import okhttp3.OkHttpClient |
| | | import okhttp3.Request |
| | | import okhttp3.Response |
| | | import org.json.JSONArray |
| | | import java.io.File |
| | | import java.io.IOException |
| | | import java.util.LinkedList |
| | | import java.util.Queue |
| | | import java.util.UUID |
| | | |
| | | class UploadSessionActivity : AppCompatActivity() { |
| | | |
| | | private lateinit var sessionProfileManager: SessionProfileManager |
| | | lateinit var sessionProfileWebView:SessionProfileWebView |
| | | |
| | | private lateinit var uploadSessionText :TextView |
| | | private lateinit var downloadHelper:DownloadHelper |
| | | |
| | | private val uploadTaskQueue: Queue<Utils.VideoUploadTaskInfo> = LinkedList() // 存储上传任务队列 |
| | | |
| | | private lateinit var sessionProfiles: java.util.ArrayList<SessionProfile> |
| | | |
| | | private var last_uploaded_task_id:String = "" |
| | | |
| | | private val threadHandler = Handler(Looper.getMainLooper()) |
| | | |
| | | private lateinit var activityReference: UploadSessionActivity |
| | | |
| | | override fun onCreate(savedInstanceState: Bundle?) { |
| | | super.onCreate(savedInstanceState) |
| | | setContentView(R.layout.upload_session) |
| | | |
| | | activityReference = this |
| | | // 初始化 SessionProfileManager |
| | | sessionProfileManager = SessionProfileManager(this) |
| | | //downloadReceiver = DownloadReceiver(this,) |
| | | uploadSessionText = findViewById(R.id.uploadSessionText) |
| | | sessionProfileWebView = findViewById(R.id.uploadSessionWebView) |
| | | // 获取传递的 SessionProfile 数组 |
| | | sessionProfiles = |
| | | intent.getParcelableArrayListExtra<SessionProfile>("sessionProfiles") |
| | | ?: arrayListOf() |
| | | if (sessionProfiles != null) { |
| | | for (profile in sessionProfiles) { |
| | | processProfile(profile) |
| | | } |
| | | } else { |
| | | Toast.makeText(this, "未接收到任何 Profile 数据", Toast.LENGTH_SHORT).show() |
| | | } |
| | | downloadHelper = DownloadHelper(this) |
| | | |
| | | |
| | | |
| | | //sessionProfileWebView.setInitialScale(75) // 初始缩放比例 |
| | | |
| | | sessionProfileWebView.webViewClient = object : WebViewClient() { |
| | | override fun onPageStarted( |
| | | view: WebView?, |
| | | url: String?, |
| | | favicon: android.graphics.Bitmap? |
| | | ) { |
| | | super.onPageStarted(view, url, favicon) |
| | | } |
| | | override fun onPageFinished(view: WebView?, url: String?) { |
| | | super.onPageFinished(view, url) |
| | | view?.requestFocus() // 确保 WebView 获取焦点 |
| | | //inject action |
| | | val currentTask = uploadTaskQueue.peek() |
| | | when (currentTask?.platform?.name) { |
| | | "抖音" -> { |
| | | val action = |
| | | ActionDouyin(activityReference, threadHandler, currentTask) |
| | | action.checkIfContentLoaded() |
| | | } |
| | | "快手" -> { |
| | | val action = |
| | | ActionKuaishou(activityReference, threadHandler, currentTask) |
| | | action.checkIfContentLoaded() |
| | | } |
| | | "视频号" -> { |
| | | val action = |
| | | ActionShipinhao(activityReference, threadHandler, currentTask) |
| | | action.checkIfContentLoaded() |
| | | } |
| | | } |
| | | url?.let{ |
| | | Log.e("shsening", url) |
| | | try { |
| | | val cookies = CookieManager.getInstance().getCookie(url) |
| | | if (cookies != null) { |
| | | CookieHelper.saveSessionProfileUrlCookies( |
| | | activityReference, |
| | | currentTask.sessionProfileId!!, |
| | | url, |
| | | cookies |
| | | ) |
| | | } else { |
| | | Log.w("shsening", "No cookies found for URL: $url") |
| | | } |
| | | } catch (e: java.lang.Exception) { |
| | | Log.e("CookieHelper", "Failed to get cookies: ", e) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | sessionProfileWebView.webChromeClient = object : WebChromeClient() { |
| | | override fun onShowFileChooser( |
| | | webView: WebView, |
| | | filePathCallback: ValueCallback<Array<Uri>>, |
| | | fileChooserParams: FileChooserParams |
| | | ): Boolean { |
| | | try { |
| | | var mFilePathCallback: ValueCallback<Array<Uri>>? = filePathCallback |
| | | // 选择特定的文件 |
| | | val currentTask = uploadTaskQueue.peek() |
| | | val download_dir = File(getExternalFilesDir(null), currentTask.sessionProfileId) |
| | | val file = File(download_dir, currentTask.filename + ".mp4") |
| | | val uri: Uri = |
| | | FileProvider.getUriForFile(activityReference, AppConfig.FILE_PROVIDER, file) |
| | | |
| | | mFilePathCallback?.onReceiveValue(arrayOf(uri)) |
| | | return true |
| | | }catch(e:Exception) { |
| | | return false |
| | | } |
| | | } |
| | | override fun onShowCustomView(view: View?, callback: CustomViewCallback?) { |
| | | super.onShowCustomView(view, callback) |
| | | view?.requestFocus() |
| | | } |
| | | } |
| | | sessionProfileWebView.isFocusableInTouchMode = true |
| | | |
| | | findViewById<Button>(R.id.btn_upload_back).setOnClickListener { |
| | | finish() |
| | | } |
| | | } |
| | | |
| | | private fun processProfile(profile:SessionProfile){ |
| | | val downloadDir = File(getExternalFilesDir(null), profile.id) // 获取应用的外部存储目录,并指定 sessionid 子目录 |
| | | |
| | | Utils.fetchTasksBySessionProfile(profile, object:Utils.FetchTasksCallback { |
| | | override fun onTasksFetched(uploadTaskList: MutableList<Utils.VideoUploadTaskInfo>) { |
| | | uploadTaskList.forEach { task -> |
| | | downloadHelper.downloadSingleFile(task.videoUrl, File(downloadDir,task.filename+".mp4")) |
| | | downloadHelper.downloadSingleFile(task.coverUrl, File(downloadDir,task.filename+".png")) |
| | | profile.getValidProfileNames().forEach{ platform -> |
| | | var taskInfo = Utils.VideoUploadTaskInfo(task.taskId, |
| | | task.title, |
| | | task.description, |
| | | task.datetime, |
| | | task.videoUrl, |
| | | task.coverUrl, |
| | | task.filename, |
| | | profile.id, |
| | | profile.name, |
| | | SESSION_PROFILE_PALTFORM_LISTS.find { it.name == platform } |
| | | ) |
| | | uploadTaskQueue.offer(taskInfo) |
| | | } |
| | | |
| | | } |
| | | Handler(Looper.getMainLooper()).postDelayed({ |
| | | checkAndStartNextTask() // 延时等待下载任务 |
| | | }, 3000) |
| | | } |
| | | }) |
| | | } |
| | | fun checkAndStartNextTask() { |
| | | if (uploadTaskQueue.isNotEmpty()) { |
| | | val nextTask = uploadTaskQueue.poll() |
| | | nextTask?.let { |
| | | nextTask.sessionProfileId?.let { sessionId -> |
| | | runOnUiThread { |
| | | if (last_uploaded_task_id != "" && last_uploaded_task_id != nextTask.taskId) { |
| | | Utils.sendVideoUploadedMessage(last_uploaded_task_id) { result, error -> |
| | | Handler(Looper.getMainLooper()).post { |
| | | Toast.makeText( |
| | | activityReference, |
| | | error?.message, |
| | | Toast.LENGTH_SHORT |
| | | ) |
| | | } |
| | | } |
| | | } else { |
| | | last_uploaded_task_id = nextTask.taskId |
| | | } |
| | | nextTask.platform?.let { |
| | | sessionProfileWebView.loadSessionProfileCookies(sessionId) |
| | | sessionProfileWebView.loadUrl(it.url) |
| | | Handler(Looper.getMainLooper()).post { |
| | | uploadSessionText.text = "任务:${nextTask.sessionProfileName}. 剩余:${uploadTaskQueue.size+1} " |
| | | } |
| | | }; |
| | | } |
| | | } |
| | | } |
| | | }else{ |
| | | Handler(Looper.getMainLooper()).post { |
| | | uploadSessionText.text = "任务已结束。" |
| | | } |
| | | Handler(Looper.getMainLooper()).postDelayed({ |
| | | if (sessionProfiles != null) { |
| | | for (profile in sessionProfiles) { |
| | | updateSessionProfileLastUploadTime(profile) |
| | | } |
| | | } |
| | | finish() |
| | | }, 2000) |
| | | |
| | | } |
| | | } |
| | | |
| | | |
| | | private fun updateSessionProfileLastUploadTime(sessionProfile: SessionProfile) { |
| | | sessionProfile.lastUploadTime = Utils.getCurrentTimeString() |
| | | sessionProfileManager.addOrUpdateSessionProfile(sessionProfile) |
| | | sessionProfileManager.saveToFile() |
| | | } |
| | | |
| | | override fun onDestroy() { |
| | | super.onDestroy() |
| | | } |
| | | } |
| | |
| | | } |
| | | """.trimIndent() |
| | | webView.evaluateJavascript(clickScript, null) |
| | | |
| | | } |
| | | |
| | | } |
| File was renamed from app/src/main/java/com/shsening/autouploadvideos/ActionDouyin.kt |
| | |
| | | package com.shsening.autouploadvideos |
| | | package com.shsening.autouploadvideos.action |
| | | |
| | | |
| | | import android.app.Instrumentation |
| | | import android.content.Context |
| | | import android.os.SystemClock |
| | | import android.os.Handler |
| | | import com.shsening.autouploadvideos.UploadSessionActivity |
| | | import com.shsening.autouploadvideos.utils.Utils |
| | | import com.shsening.autouploadvideos.WebviewUtils |
| | | |
| | | class ActionDouyin (private var fragment:UploadFragment, private var context:Context, private var threadHandler: Handler, private var videoInfo:Utils.VideoUploadTaskInfo){ |
| | | class ActionDouyin (private var activity: UploadSessionActivity, private var threadHandler: Handler, private var videoInfo: Utils.VideoUploadTaskInfo) :PlatformActionInterface{ |
| | | |
| | | |
| | | private var webView = fragment.webView |
| | | private var webView = activity.sessionProfileWebView |
| | | |
| | | |
| | | var jsCode_CheckContentLoaded = """ |
| | |
| | | |
| | | |
| | | private fun updatePostDescription(){ |
| | | WebviewUtils.addValueToInput(webView,"#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.publish-mention-wrapper-LWv5ed > div.editor-container-zRPSAi > div > div > div > div:nth-child(1) > div > div > input",videoInfo.title) |
| | | WebviewUtils.addValueToInput( |
| | | webView, |
| | | "#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.publish-mention-wrapper-LWv5ed > div.editor-container-zRPSAi > div > div > div > div:nth-child(1) > div > div > input", |
| | | videoInfo.title |
| | | ) |
| | | |
| | | WebviewUtils.addValueToDiv(webView,"#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.publish-mention-wrapper-LWv5ed > div.editor-container-zRPSAi > div > div > div > div.outerdocbody.editor-kit-outer-container > div",videoInfo.description) |
| | | WebviewUtils.addValueToDiv( |
| | | webView, |
| | | "#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.publish-mention-wrapper-LWv5ed > div.editor-container-zRPSAi > div > div > div > div.outerdocbody.editor-kit-outer-container > div", |
| | | videoInfo.description |
| | | ) |
| | | |
| | | WebviewUtils.simpleElementClick(webView,"#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.publish-mention-wrapper-LWv5ed > div:nth-child(3) > div > div.activity-list-container-AZzfdV > div:nth-child(1)") |
| | | WebviewUtils.simpleElementClick(webView,"#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.publish-mention-wrapper-LWv5ed > div:nth-child(3) > div > div.activity-list-container-AZzfdV > div:nth-child(2)") |
| | | WebviewUtils.simpleElementClick( |
| | | webView, |
| | | "#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.publish-mention-wrapper-LWv5ed > div:nth-child(3) > div > div.activity-list-container-AZzfdV > div:nth-child(1)" |
| | | ) |
| | | WebviewUtils.simpleElementClick( |
| | | webView, |
| | | "#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.publish-mention-wrapper-LWv5ed > div:nth-child(3) > div > div.activity-list-container-AZzfdV > div:nth-child(2)" |
| | | ) |
| | | |
| | | WebviewUtils.simpleElementClick(webView,"#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.content-upload-new > div > div.recommendContainer-HXvL9W > div.recommendDisplay-ifUYki > div > div:nth-child(1) > div") |
| | | WebviewUtils.simpleElementClick(webView,"#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.download-content-Lci5tL > div > label:nth-child(2)") |
| | | WebviewUtils.simpleElementClick(webView,"#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.publish-settings-WtW_ZW > div.schedule-part-V615Zn > div.row-suTOx_ > label:nth-child(2)") |
| | | WebviewUtils.simpleElementClick( |
| | | webView, |
| | | "#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.content-upload-new > div > div.recommendContainer-HXvL9W > div.recommendDisplay-ifUYki > div > div:nth-child(1) > div" |
| | | ) |
| | | WebviewUtils.simpleElementClick( |
| | | webView, |
| | | "#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.download-content-Lci5tL > div > label:nth-child(2)" |
| | | ) |
| | | WebviewUtils.simpleElementClick( |
| | | webView, |
| | | "#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.publish-settings-WtW_ZW > div.schedule-part-V615Zn > div.row-suTOx_ > label:nth-child(2)" |
| | | ) |
| | | |
| | | addValueToDatePickerInput(true,"#root > div > div > div.content-body-v8tuJQ > div.form-kSES6A > div.publish-settings-WtW_ZW > div.schedule-part-V615Zn > div.row-suTOx_ > div.date-picker-ioPchj > div > div",videoInfo.datetime) |
| | | |
| | |
| | | } |
| | | |
| | | |
| | | fun checkIfContentLoaded() { |
| | | override fun checkIfContentLoaded() { |
| | | webView.postDelayed({ |
| | | webView.evaluateJavascript(jsCode_CheckContentLoaded) { result -> |
| | | if (result == "true") { |
| | |
| | | if (result == "true") { |
| | | |
| | | // video published successfully!!! |
| | | fragment.startUploadSingleTask() |
| | | activity.checkAndStartNextTask() |
| | | |
| | | |
| | | } else { |
| | |
| | | |
| | | private fun simulateSwipeAndUploadVideo() { |
| | | val instrumentation = Instrumentation() |
| | | val displayMetrics = context.resources.displayMetrics |
| | | val displayMetrics = activity.resources.displayMetrics |
| | | val width = displayMetrics.widthPixels |
| | | val height = displayMetrics.heightPixels |
| | | |
| File was renamed from app/src/main/java/com/shsening/autouploadvideos/ActionKuaishou.kt |
| | |
| | | package com.shsening.autouploadvideos |
| | | package com.shsening.autouploadvideos.action |
| | | |
| | | |
| | | import android.app.Instrumentation |
| | | import android.content.Context |
| | | import android.os.SystemClock |
| | | import android.os.Handler |
| | | import com.shsening.autouploadvideos.UploadFragment |
| | | import com.shsening.autouploadvideos.UploadSessionActivity |
| | | import com.shsening.autouploadvideos.utils.Utils |
| | | import com.shsening.autouploadvideos.WebviewUtils |
| | | |
| | | class ActionKuaishou (private var fragment:UploadFragment, private var context:Context, private var threadHandler: Handler, private var videoInfo:Utils.VideoUploadTaskInfo){ |
| | | class ActionKuaishou (private var activity: UploadSessionActivity, private var threadHandler: Handler, private var videoInfo: Utils.VideoUploadTaskInfo) :PlatformActionInterface{ |
| | | |
| | | |
| | | private var webView = fragment.webView |
| | | private var webView = activity.sessionProfileWebView |
| | | |
| | | |
| | | var jsCode_CheckContentLoaded = """ |
| | |
| | | WebviewUtils.addValueToDiv(webView,"#work-description-edit",videoInfo.description) |
| | | |
| | | // do not allow download |
| | | WebviewUtils.simpleElementClick(webView,"#setting-tours > div:nth-child(2) > div > label:nth-child(2)") |
| | | WebviewUtils.simpleElementClick( |
| | | webView, |
| | | "#setting-tours > div:nth-child(2) > div > label:nth-child(2)" |
| | | ) |
| | | |
| | | // do not show same city |
| | | WebviewUtils.simpleElementClick(webView,"#setting-tours > div:nth-child(2) > div > label:nth-child(3)") |
| | | WebviewUtils.simpleElementClick( |
| | | webView, |
| | | "#setting-tours > div:nth-child(2) > div > label:nth-child(3)" |
| | | ) |
| | | |
| | | //Scheduled publish |
| | | WebviewUtils.simpleElementClick(webView,"#setting-tours > div:nth-child(4) > div > div > label:nth-child(2)") |
| | | WebviewUtils.simpleElementClick( |
| | | webView, |
| | | "#setting-tours > div:nth-child(4) > div > div > label:nth-child(2)" |
| | | ) |
| | | |
| | | addValueToDatePickerInput(true,"#setting-tours > div:nth-child(4) > div > div:nth-child(2) > div > div > input",videoInfo.datetime) |
| | | // 在主线程中设置延迟执行 |
| | |
| | | } |
| | | |
| | | |
| | | fun checkIfContentLoaded() { |
| | | override fun checkIfContentLoaded() { |
| | | webView.postDelayed({ |
| | | webView.evaluateJavascript(jsCode_CheckContentLoaded) { result -> |
| | | if (result == "true") { |
| | |
| | | webView.evaluateJavascript(jsCode_PublishVideo_Finished) { result -> |
| | | if (result == "true") { |
| | | // video published successfully!!! |
| | | fragment.startUploadSingleTask() |
| | | activity.checkAndStartNextTask() |
| | | } else { |
| | | // 元素不存在,1秒后再次检查 |
| | | webView.postDelayed({ |
| | |
| | | |
| | | private fun simulateSwipeAndUploadVideo() { |
| | | val instrumentation = Instrumentation() |
| | | val displayMetrics = context.resources.displayMetrics |
| | | val displayMetrics = activity.resources.displayMetrics |
| | | val width = displayMetrics.widthPixels |
| | | val height = displayMetrics.heightPixels |
| | | |
| File was renamed from app/src/main/java/com/shsening/autouploadvideos/ActionShipinhao.kt |
| | |
| | | package com.shsening.autouploadvideos |
| | | package com.shsening.autouploadvideos.action |
| | | |
| | | |
| | | import android.app.Instrumentation |
| | | import android.content.Context |
| | | import android.os.SystemClock |
| | | import android.os.Handler |
| | | import com.shsening.autouploadvideos.UploadFragment |
| | | import com.shsening.autouploadvideos.UploadSessionActivity |
| | | import com.shsening.autouploadvideos.utils.Utils |
| | | import com.shsening.autouploadvideos.WebviewUtils |
| | | |
| | | class ActionShipinhao (private var fragment:UploadFragment, private var context:Context, private var threadHandler: Handler, private var videoInfo:Utils.VideoUploadTaskInfo){ |
| | | class ActionShipinhao (private var activity: UploadSessionActivity, private var threadHandler: Handler, private var videoInfo: Utils.VideoUploadTaskInfo) :PlatformActionInterface{ |
| | | |
| | | |
| | | private var webView = fragment.webView |
| | | private var webView = activity.sessionProfileWebView |
| | | |
| | | |
| | | var jsCode_CheckContentLoaded = """ |
| | |
| | | * Title,description(including # and @ ) publish datetime etc |
| | | */ |
| | | private fun updatePostDescription(){ |
| | | WebviewUtils.addValueToDiv(webView,"#container-wrap > div.container-center > div > div > div.main-body-wrap.post-create > div.main-body > div > div.post-edit-wrap.material-edit-wrap > div.form > div:nth-child(2) > div.form-item-body > div > div.input-editor",videoInfo.description) |
| | | WebviewUtils.addValueToDiv( |
| | | webView, |
| | | "#container-wrap > div.container-center > div > div > div.main-body-wrap.post-create > div.main-body > div > div.post-edit-wrap.material-edit-wrap > div.form > div:nth-child(2) > div.form-item-body > div > div.input-editor", |
| | | videoInfo.description |
| | | ) |
| | | |
| | | |
| | | // publish later radiobox |
| | | WebviewUtils.simpleElementClick(webView,"#container-wrap > div.container-center > div > div > div.main-body-wrap.post-create > div.main-body > div > div.post-edit-wrap.material-edit-wrap > div.form > div.post-time-wrap > div:nth-child(1) > div.form-item-body > div > label:nth-child(2)") |
| | | WebviewUtils.simpleElementClick( |
| | | webView, |
| | | "#container-wrap > div.container-center > div > div > div.main-body-wrap.post-create > div.main-body > div > div.post-edit-wrap.material-edit-wrap > div.form > div.post-time-wrap > div:nth-child(1) > div.form-item-body > div > label:nth-child(2)" |
| | | ) |
| | | |
| | | addValueToDatePickerInput(true,"#container-wrap > div.container-center > div > div > div.main-body-wrap.post-create > div.main-body > div > div.post-edit-wrap.material-edit-wrap > div.form > div.post-time-wrap > div:nth-child(2) > div.form-item-body > dl > dd",videoInfo.datetime) |
| | | |
| | |
| | | } |
| | | |
| | | |
| | | fun checkIfContentLoaded() { |
| | | override fun checkIfContentLoaded() { |
| | | webView.postDelayed({ |
| | | webView.evaluateJavascript(jsCode_CheckContentLoaded) { result -> |
| | | if (result == "true") { |
| | |
| | | webView.evaluateJavascript(jsCode_PublishVideo_Finished) { result -> |
| | | if (result == "true") { |
| | | // video published successfully!!! |
| | | fragment.startUploadSingleTask() |
| | | activity.checkAndStartNextTask() |
| | | } else { |
| | | // 元素不存在,1秒后再次检查 |
| | | webView.postDelayed({ |
| | |
| | | |
| | | private fun simulateSwipeAndUploadVideo() { |
| | | val instrumentation = Instrumentation() |
| | | val displayMetrics = context.resources.displayMetrics |
| | | val displayMetrics = activity.resources.displayMetrics |
| | | val width = displayMetrics.widthPixels |
| | | val height = displayMetrics.heightPixels |
| | | |
| New file |
| | |
| | | package com.shsening.autouploadvideos.action |
| | | |
| | | interface PlatformActionInterface { |
| | | |
| | | fun checkIfContentLoaded() |
| | | |
| | | } |
| New file |
| | |
| | | package com.shsening.autouploadvideos.utils |
| | | |
| | | import android.app.DownloadManager |
| | | import android.content.BroadcastReceiver |
| | | import android.content.Context |
| | | import android.content.Intent |
| | | import android.content.IntentFilter |
| | | import android.widget.Toast |
| | | |
| | | class DownloadReceiver(private val context: Context,private val downloadHelper: DownloadHelper) : BroadcastReceiver() { |
| | | |
| | | init { |
| | | context.registerReceiver(this, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) |
| | | } |
| | | override fun onReceive(context: Context, intent: Intent) { |
| | | val action = intent.action |
| | | if (DownloadManager.ACTION_DOWNLOAD_COMPLETE == action) { |
| | | val downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) |
| | | if (downloadId != -1L) { |
| | | // downoad finished |
| | | } |
| | | } |
| | | } |
| | | |
| | | fun unregister() { |
| | | context.unregisterReceiver(this) |
| | | } |
| | | } |
| New file |
| | |
| | | package com.shsening.autouploadvideos.utils |
| | | |
| | | import android.app.DownloadManager |
| | | import android.content.Context |
| | | import android.net.Uri |
| | | import android.os.Environment |
| | | import android.widget.Toast |
| | | import java.io.File |
| | | |
| | | class DownloadHelper(private val context: Context) { |
| | | |
| | | private val downloadManager = |
| | | context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager |
| | | |
| | | fun downloadSingleFile(fileUrl:String,fileUri:File) { |
| | | try { |
| | | if (fileUri.exists()) { |
| | | return |
| | | } |
| | | // 确保目标文件所在的目录存在 |
| | | val parentDir = fileUri.parentFile |
| | | if (parentDir != null && !parentDir.exists()) { |
| | | parentDir.mkdirs() // 创建目录 |
| | | } |
| | | val request = DownloadManager.Request(Uri.parse(fileUrl)).apply { |
| | | setTitle("Downloading ${fileUri.name}") |
| | | setDescription("Downloading file...") |
| | | .setDestinationUri(Uri.fromFile(fileUri)) // 设置下载文件保存路径 |
| | | setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) |
| | | setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE) |
| | | |
| | | } |
| | | // 将下载请求加入队列 |
| | | downloadManager.enqueue(request) |
| | | |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | Toast.makeText(context, "Failed to download: $fileUrl", Toast.LENGTH_SHORT).show() |
| | | } |
| | | } |
| | | } |
| File was renamed from app/src/main/java/com/shsening/autouploadvideos/Utils.kt |
| | |
| | | package com.shsening.autouploadvideos |
| | | package com.shsening.autouploadvideos.utils |
| | | |
| | | |
| | | import SessionProfile |
| | | import android.app.Instrumentation |
| | | import android.content.Context |
| | | import android.content.Intent |
| | | import android.os.SystemClock |
| | | import android.view.MotionEvent |
| | | import android.webkit.CookieManager |
| | | import android.widget.Toast |
| | | import androidx.core.content.FileProvider |
| | | import com.shsening.autouploadvideos.AppConfig |
| | | import com.shsening.autouploadvideos.PlatformItem |
| | | import kotlinx.coroutines.CompletableDeferred |
| | | import kotlinx.coroutines.Dispatchers |
| | | import kotlinx.coroutines.withContext |
| | |
| | | import okhttp3.OkHttpClient |
| | | import okhttp3.Request |
| | | import okhttp3.Response |
| | | import org.json.JSONArray |
| | | import org.json.JSONObject |
| | | import java.io.File |
| | | import java.io.IOException |
| | | import java.io.Serializable |
| | | import java.text.SimpleDateFormat |
| | | import java.util.Calendar |
| | | import java.util.Date |
| | | import java.util.Locale |
| | | |
| | | object Utils { |
| | | data class AppUpdateInfo( |
| | |
| | | val datetime: String, |
| | | val videoUrl: String, |
| | | val coverUrl: String, |
| | | var filename: String = generateFilename(datetime) |
| | | var filename: String = generateFilename(datetime), |
| | | var sessionProfileId: String? =null, |
| | | var sessionProfileName: String? =null, |
| | | var platform: PlatformItem? =null |
| | | ) : Serializable { |
| | | |
| | | companion object { |
| | |
| | | |
| | | return arrayOf(row, column) // 返回数组 |
| | | } |
| | | suspend fun sendVideoUploadedMessage(context: Context,taskId: String): Boolean { |
| | | fun sendVideoUploadedMessage(taskId: String, callback: (Boolean, Exception?) -> Unit) { |
| | | try { |
| | | val client = OkHttpClient() |
| | | val request = Request.Builder() |
| | | .url(AppConfig.URL_CONFIRM_TASK_UPLOADED + taskId) |
| | | .build() |
| | | |
| | | return withContext(Dispatchers.IO) { |
| | | |
| | | val response = client.newCall(request).execute() |
| | | if (!response.isSuccessful) throw IOException("Unexpected code $response") |
| | | val json = JSONObject(response.body?.string() ?: "") |
| | | |
| | | json.getBoolean("result") |
| | | // 异步执行网络请求 |
| | | client.newCall(request).enqueue(object : okhttp3.Callback { |
| | | override fun onFailure(call: okhttp3.Call, e: IOException) { |
| | | // 请求失败,返回错误 |
| | | callback(false, e) |
| | | } |
| | | |
| | | override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) { |
| | | try { |
| | | val json = JSONObject(response.body?.string() ?: "") |
| | | val result = json.getString("msg") |
| | | // 成功回调 |
| | | callback(true, Exception(result)) |
| | | } catch (e: Exception) { |
| | | // 处理解析错误 |
| | | callback(false, e) |
| | | } finally { |
| | | response.close() |
| | | } |
| | | } |
| | | }) |
| | | }catch(e:Exception){ |
| | | e.printStackTrace() |
| | | |
| | | return false |
| | | |
| | | // 处理其他异常 |
| | | callback(false, e) |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | } |
| | | } |
| | | interface FetchTasksCallback { |
| | | fun onTasksFetched(uploadTaskList: MutableList<Utils.VideoUploadTaskInfo>) |
| | | } |
| | | interface UploadCallback { |
| | | fun onSuccess() |
| | | fun onError() |
| | | } |
| | | |
| | | fun fetchTasksBySessionProfile(profile: SessionProfile, callback: FetchTasksCallback) { |
| | | val uploadTaskList: MutableList<Utils.VideoUploadTaskInfo> = mutableListOf() |
| | | |
| | | val client = OkHttpClient() |
| | | val request = Request.Builder() |
| | | .url(AppConfig.URL_CHECK_CLIENT_TASKS + AppConfig.CLIENT_ANDROID_ID + "&sessionprofile=" + profile.id + "&videotype=" + profile.type) // 替换为你的任务地址 |
| | | .build() |
| | | |
| | | client.newCall(request).enqueue(object : Callback { |
| | | override fun onFailure(call: Call, e: IOException) { |
| | | e.printStackTrace() |
| | | // 可以在这里通知调用者出错 |
| | | } |
| | | |
| | | override fun onResponse(call: Call, response: Response) { |
| | | response.use { |
| | | if (!response.isSuccessful) throw IOException("Unexpected code $response") |
| | | val jsonArray = JSONArray(response.body?.string() ?: "") |
| | | if (jsonArray.length() > 0) { |
| | | for (i in 0 until jsonArray.length()) { |
| | | val jsonObject = jsonArray.getJSONObject(i) |
| | | try { |
| | | val task = Utils.VideoUploadTaskInfo( |
| | | taskId = jsonObject.getString("taskId"), |
| | | title = jsonObject.getString("title"), |
| | | description = jsonObject.getString("description"), |
| | | datetime = jsonObject.getString("datetime"), |
| | | videoUrl = jsonObject.getString("videoUrl"), |
| | | coverUrl = jsonObject.getString("coverUrl") |
| | | ) |
| | | uploadTaskList.add(task) |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | } |
| | | } |
| | | } |
| | | } |
| | | // 在请求成功并解析完任务列表后,调用回调函数 |
| | | callback.onTasksFetched(uploadTaskList) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | suspend fun downloadCommonFileToExternalFilesDir(context: Context, url: String, filename: String, install: Boolean) { |
| | | val tmpFile = File(context.getExternalFilesDir(null), filename) |
| | |
| | | override fun onFailure(call: Call, e: IOException) { |
| | | deferred.completeExceptionally(e) // 下载失败时标记为异常 |
| | | } |
| | | |
| | | override fun onResponse(call: Call, response: Response) { |
| | | response.use { |
| | | if (!response.isSuccessful) { |
| | | deferred.completeExceptionally(IOException("Unexpected code $response")) // 如果响应不成功,标记为异常 |
| | | return |
| | | } |
| | | |
| | | tmpFile.outputStream().use { outputStream -> |
| | | outputStream.write(response.body?.bytes()) |
| | | } |
| | | |
| | | if (install) { |
| | | installApk(context,tmpFile) |
| | | } |
| | |
| | | return if (month in 1..12) months[month - 1] else "Invalid" |
| | | } |
| | | |
| | | // 获取 key 的位置 |
| | | fun getSessionProfileValuePosition(key: String): Int { |
| | | val index = AppConfig.SESSION_PROFILE_TYPE.values.toList().indexOf(key) |
| | | return if (index == -1) 0 else index |
| | | } |
| | | |
| | | fun getCurrentTimeString(): String { |
| | | val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) // 自定义格式 |
| | | val currentDate = Date() // 获取当前时间 |
| | | return formatter.format(currentDate) // 格式化为字符串 |
| | | } |
| | | |
| | | // 根据位置获取 key |
| | | fun getSessionProfileValueByPosition(position: Int?): String? { |
| | | if (position==null){ |
| | | return "Unknown" |
| | | } |
| | | return AppConfig.SESSION_PROFILE_TYPE.values.toList().getOrElse(position) { "Unknown" } |
| | | } |
| | | |
| | | } |
| | |
| | | android:text="@string/app_tab_upload"/> |
| | | |
| | | <Button |
| | | android:id="@+id/tabHelp" |
| | | android:id="@+id/tabSetting" |
| | | android:layout_width="0dp" |
| | | android:layout_height="48dp" |
| | | android:layout_weight="1" |
| New file |
| | |
| | | |
| | | <LinearLayout |
| | | xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:orientation="vertical" |
| | | android:padding="6dp"> |
| | | |
| | | <RelativeLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content"> |
| | | <!-- 左边按钮:返回 --> |
| | | <Button |
| | | android:id="@+id/btn_back" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="返回" |
| | | android:layout_alignParentStart="true" /> <!-- 靠左对齐 --> |
| | | <Button |
| | | android:id="@+id/btn_save" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:text="保存配置" |
| | | android:layout_alignParentEnd="true" /> <!-- 靠右对齐 --> |
| | | |
| | | </RelativeLayout> |
| | | <RelativeLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content"> |
| | | |
| | | <TextView |
| | | android:id="@+id/txt_session_id" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:layout_centerInParent="true" |
| | | android:gravity="center"/> |
| | | |
| | | </RelativeLayout> |
| | | |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:weightSum="2" |
| | | android:padding="2dp"> |
| | | |
| | | <TextView |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" |
| | | android:text="Name:" /> |
| | | |
| | | <EditText |
| | | android:id="@+id/edit_name" |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" /> |
| | | </LinearLayout> |
| | | <LinearLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="horizontal" |
| | | android:weightSum="2" |
| | | android:padding="2dp"> |
| | | |
| | | <TextView |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" |
| | | android:text="Type:" /> |
| | | |
| | | <Spinner |
| | | android:id="@+id/spinner_type" |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" /> |
| | | </LinearLayout> |
| | | |
| | | <LinearLayout |
| | | android:id="@+id/switch_container" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:orientation="vertical" |
| | | android:paddingTop="16dp" /> |
| | | |
| | | <ProgressBar |
| | | android:id="@+id/progressBar" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_centerInParent="true" |
| | | android:visibility="gone" /> |
| | | |
| | | |
| | | <com.shsening.autouploadvideos.SessionProfileWebView |
| | | android:id="@+id/sessionProfileWebView" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="0dp" |
| | | android:layout_weight="1" |
| | | android:focusable="true" |
| | | android:focusableInTouchMode="true" /> |
| | | </LinearLayout> |
| | | |
| | |
| | | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent"> |
| | | |
| | | <LinearLayout |
| | | android:orientation="vertical" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:padding="16dp"> |
| | | |
| | | <TextView |
| | | android:id="@+id/androidIdText" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="18sp" |
| | | android:layout_marginBottom="16dp" /> |
| | | |
| | | <ImageView |
| | | android:id="@+id/imageView" |
| | | android:layout_width="300dp" |
| | | android:layout_height="300dp" |
| | | android:scaleType="centerCrop" |
| | | android:layout_marginBottom="16dp" /> |
| | | |
| | | <TextView |
| | | android:id="@+id/descriptionText" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="18sp" |
| | | android:gravity="center" |
| | | android:layout_marginBottom="16dp" /> |
| | | |
| | | <!-- 使用固定高度代替 wrap_content --> |
| | | <FrameLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent"> |
| | | |
| | |
| | | android:id="@+id/webView" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" /> |
| | | </FrameLayout> |
| | | </LinearLayout> |
| | | </ScrollView> |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <LinearLayout |
| | | xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:orientation="vertical"> |
| | | |
| | | <RelativeLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:padding="16dp"> |
| | | |
| | | <Button |
| | | android:id="@+id/btn_new" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:layout_alignParentEnd="true" |
| | | android:layout_marginEnd="16dp" |
| | | android:text="新建" /> |
| | | </RelativeLayout> |
| | | |
| | | <ListView |
| | | android:id="@+id/list_view" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:divider="@android:color/darker_gray" |
| | | android:dividerHeight="1dp" /> |
| | | </LinearLayout> |
| New file |
| | |
| | | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:orientation="horizontal" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:padding="8dp" |
| | | android:gravity="center_vertical"> |
| | | |
| | | <!-- 左侧的 CheckBox --> |
| | | <CheckBox |
| | | android:id="@+id/itemCheckBox" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:checked="true" |
| | | android:layout_marginEnd="8dp" /> |
| | | |
| | | <LinearLayout |
| | | android:orientation="vertical" |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" |
| | | android:paddingEnd="8dp"> |
| | | |
| | | <TextView |
| | | android:id="@android:id/text1" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="16sp" |
| | | android:textColor="@android:color/black" /> |
| | | |
| | | <TextView |
| | | android:id="@android:id/text2" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="14sp" |
| | | android:textColor="@android:color/darker_gray" /> |
| | | </LinearLayout> |
| | | |
| | | <Button |
| | | android:id="@+id/deleteButton" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:focusable="false" |
| | | android:focusableInTouchMode="false" |
| | | android:clickable="true" |
| | | android:text="上传" |
| | | android:textColor="@android:color/holo_blue_light" |
| | | android:background="?android:attr/selectableItemBackground" /> |
| | | </LinearLayout> |
| New file |
| | |
| | | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:orientation="horizontal" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:padding="8dp" |
| | | android:gravity="center_vertical"> |
| | | |
| | | <LinearLayout |
| | | android:orientation="vertical" |
| | | android:layout_width="0dp" |
| | | android:layout_height="wrap_content" |
| | | android:layout_weight="1" |
| | | android:paddingEnd="8dp"> |
| | | |
| | | <TextView |
| | | android:id="@android:id/text1" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="16sp" |
| | | android:textColor="@android:color/black" /> |
| | | |
| | | <TextView |
| | | android:id="@android:id/text2" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:textSize="14sp" |
| | | android:textColor="@android:color/darker_gray" /> |
| | | </LinearLayout> |
| | | |
| | | <Button |
| | | android:id="@+id/deleteButton" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:focusable="false" |
| | | android:focusableInTouchMode="false" |
| | | android:clickable="true" |
| | | android:text="删除" |
| | | android:textColor="@android:color/holo_blue_light" |
| | | android:background="?android:attr/selectableItemBackground" /> |
| | | </LinearLayout> |
| New file |
| | |
| | | <?xml version="1.0" encoding="utf-8"?> |
| | | <LinearLayout |
| | | xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:orientation="vertical"> |
| | | <Button |
| | | android:id="@+id/btn_all_task" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="48dp" |
| | | android:background="@color/teal_200" |
| | | android:text="一键上传所有任务" /> |
| | | |
| | | |
| | | <ListView |
| | | android:id="@+id/upload_list_view" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:divider="@android:color/darker_gray" |
| | | android:dividerHeight="1dp" /> |
| | | </LinearLayout> |
| New file |
| | |
| | | |
| | | <LinearLayout |
| | | xmlns:android="http://schemas.android.com/apk/res/android" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="match_parent" |
| | | android:orientation="vertical" |
| | | android:padding="6dp"> |
| | | |
| | | |
| | | <RelativeLayout |
| | | android:layout_width="match_parent" |
| | | android:layout_height="wrap_content" |
| | | android:gravity="center_vertical"> |
| | | |
| | | <Button |
| | | android:id="@+id/btn_upload_back" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="48dp" |
| | | android:layout_alignParentStart="true" |
| | | android:gravity="center" |
| | | android:text="返回" /> |
| | | |
| | | <TextView |
| | | android:id="@+id/uploadSessionText" |
| | | android:layout_width="250dp" |
| | | android:layout_height="48dp" |
| | | android:layout_alignParentRight="true" |
| | | android:background="@color/teal_200" |
| | | android:gravity="center" /> |
| | | |
| | | </RelativeLayout> |
| | | |
| | | <com.shsening.autouploadvideos.SessionProfileWebView |
| | | android:id="@+id/uploadSessionWebView" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="0dp" |
| | | android:layout_weight="1" |
| | | android:focusable="true" |
| | | android:focusableInTouchMode="true" /> |
| | | </LinearLayout> |
| | | |
| | |
| | | <string name="app_name">Video Upload</string> |
| | | <string name="hello_blank_fragment">Hello blank fragment</string> |
| | | <string name="app_tab_upload">上传视频</string> |
| | | <string name="app_tab_description">使用说明</string> |
| | | <string name="app_tab_my">我的</string> |
| | | <string name="app_tab_description">模板设置</string> |
| | | <string name="app_tab_my">使用说明</string> |
| | | |
| | | <string name="msg_douyin">抖音</string> |
| | | <string name="msg_kuaishou">快手</string> |
| | |
| | | appcompat="1.6.0" |
| | | constraintlayout="2.1.4" |
| | | material = "1.12.0" |
| | | |
| | | parcelize="2.1.0" |
| | | firebaseStorageKtx = "21.0.1" |
| | | [libraries] |
| | | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } |
| | | junit = { group = "junit", name = "junit", version.ref = "junit" } |
| | |
| | | appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } |
| | | constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } |
| | | material = { group = "com.google.android.material", name = "material", version.ref = "material" } |
| | | |
| | | firebase-storage-ktx = { group = "com.google.firebase", name = "firebase-storage-ktx", version.ref = "firebaseStorageKtx" } |
| | | |
| | | [plugins] |
| | | android-application = { id = "com.android.application", version.ref = "agp" } |
| | | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } |
| | | |